blob: 7d8048689afd8d5c2503ec29b4b7cd61818a4ee6 [file] [log] [blame]
# Copyright 2024 The ChromiumOS Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Tests for git_utils."""
import subprocess
import textwrap
from unittest import mock
from cros_utils import git_utils
from llvm_tools import test_helpers
# pylint: disable=protected-access
EXAMPLE_GIT_SHA = "d46d9c1a23118e3943f43fe2dfc9f9c9c0b4aefe"
GERRIT_OUTPUT_WITH_ONE_CL = r"""
Enumerating objects: 4, done.
Counting objects: 100% (4/4), done.
Delta compression using up to 128 threads
Compressing objects: 100% (2/2), done.
Writing objects: 100% (3/3), 320 bytes | 106.00 KiB/s, done.
Total 3 (delta 1), reused 1 (delta 0), pack-reused 0 (from 0)
remote: Resolving deltas: 100% (1/1)
remote: Processing changes: refs: 1, new: 1, done
remote:
remote: SUCCESS
remote:
remote: https://chromium-review.googlesource.com/c/chromiumos/third_party/toolchain-utils/+/5375204 DO NOT COMMIT [WIP] [NEW]
remote:
To https://chromium.googlesource.com/chromiumos/third_party/toolchain-utils
* [new reference] HEAD -> refs/for/main
"""
GERRIT_OUTPUT_WITH_TWO_CLS = r"""
Enumerating objects: 4, done.
Counting objects: 100% (4/4), done.
Delta compression using up to 128 threads
Compressing objects: 100% (2/2), done.
Writing objects: 100% (3/3), 320 bytes | 106.00 KiB/s, done.
Total 3 (delta 1), reused 1 (delta 0), pack-reused 0 (from 0)
remote: Resolving deltas: 100% (1/1)
remote: Processing changes: refs: 1, new: 1, done
remote:
remote: SUCCESS
remote:
remote: https://chromium-review.googlesource.com/c/chromiumos/third_party/toolchain-utils/+/5375204 DO NOT COMMIT [WIP] [NEW]
remote: https://chromium-review.googlesource.com/c/chromiumos/third_party/toolchain-utils/+/5375205 DO NOT COMMIT [WIP] [NEW]
remote:
To https://chromium.googlesource.com/chromiumos/third_party/toolchain-utils
* [new reference] HEAD -> refs/for/main
"""
GERRIT_OUTPUT_WITH_INTERNAL_CL = r"""
Upload project manifest-internal/ to remote branch refs/heads/main:
branch DO-NOT-COMMIT ( 1 commit, Tue Apr 16 08:51:25 2024 -0600):
456aadd0 DO NOT COMMIT
to https://chrome-internal-review.googlesource.com (y/N)? <--yes>
Enumerating objects: 5, done.
Counting objects: 100% (5/5), done.
Delta compression using up to 128 threads
Compressing objects: 100% (3/3), done.
Writing objects: 100% (3/3), 334 bytes | 334.00 KiB/s, done.
Total 3 (delta 2), reused 0 (delta 0), pack-reused 0 (from 0)
remote: Resolving deltas: 100% (2/2)
remote: Waiting for private key checker: 1/1 objects left
remote: Processing changes: refs: 1, new: 1, done
remote:
remote: SUCCESS
remote:
remote: https://chrome-internal-review.googlesource.com/c/chromeos/manifest-internal/+/7190037 DO NOT COMMIT [NEW]
remote:
To https://chrome-internal-review.googlesource.com/chromeos/manifest-internal
* [new reference] DO-NOT-COMMIT -> refs/for/main
----------------------------------------------------------------------
[OK ] manifest-internal/ DO-NOT-COMMIT
"""
class Test(test_helpers.TempDirTestCase):
"""Tests for git_utils."""
def test_is_full_git_sha_success_cases(self):
shas = ("a" * 40, EXAMPLE_GIT_SHA)
for s in shas:
self.assertTrue(git_utils.is_full_git_sha(s), s)
def test_is_full_git_sha_failure_cases(self):
shas = (
"",
"A" * 40,
"g" * 40,
EXAMPLE_GIT_SHA[1:],
EXAMPLE_GIT_SHA + "a",
)
for s in shas:
self.assertFalse(git_utils.is_full_git_sha(s), s)
def test_cl_parsing_complains_if_no_output(self):
with self.assertRaisesRegex(ValueError, ".*; found 0"):
git_utils._parse_cls_from_upload_output("")
def test_cl_parsing_works_with_one_cl(self):
self.assertEqual(
git_utils._parse_cls_from_upload_output(GERRIT_OUTPUT_WITH_ONE_CL),
[5375204],
)
def test_cl_parsing_works_with_two_cls(self):
self.assertEqual(
git_utils._parse_cls_from_upload_output(GERRIT_OUTPUT_WITH_TWO_CLS),
[5375204, 5375205],
)
def test_cl_parsing_works_with_internal_cl(self):
self.assertEqual(
git_utils._parse_cls_from_upload_output(
GERRIT_OUTPUT_WITH_INTERNAL_CL
),
[7190037],
)
def test_parse_message_metadata(self):
"""Test we can parse commit metadata."""
message_lines = [
"Some subject line here",
"",
"Here is my commit message!",
"",
"BUG=None",
"TEST=None",
"",
"patch.cherry: true",
"patch.version_range.from: 1245",
"patch.version_range.until: null",
"Commit-Id: abcdef1234567890",
]
parsed = git_utils.parse_message_metadata(message_lines)
self.assertEqual(parsed["patch.cherry"], "true")
self.assertEqual(parsed["patch.version_range.from"], "1245")
self.assertEqual(parsed["patch.version_range.until"], "null")
self.assertEqual(parsed.get("BUG"), None)
def test_channel_parsing(self):
with self.assertRaisesRegex(ValueError, "No such channel.*"):
git_utils.Channel.parse("not a channel")
# Ensure these round-trip.
for channel in git_utils.Channel:
self.assertEqual(channel, git_utils.Channel.parse(channel.value))
@mock.patch.object(subprocess, "run")
def test_branch_autodetection(self, subprocess_run):
subprocess_run.return_value = subprocess.CompletedProcess(
args=[],
returncode=0,
stdout=textwrap.dedent(
"""
cros/not-a-release-branch
cros/release-R121-15699.B
cros/release-R122-15753.B
cros/release-R123-15786.B
cros/also-not-a-release-branch
m/main
"""
),
)
branch_dict = git_utils.autodetect_cros_channels(
git_repo=self.make_tempdir()
)
self.assertEqual(
branch_dict,
{
git_utils.Channel.CANARY: git_utils.ChannelBranch(
remote="cros",
release_number=124,
branch_name="main",
),
git_utils.Channel.BETA: git_utils.ChannelBranch(
remote="cros",
release_number=123,
branch_name="release-R123-15786.B",
),
git_utils.Channel.STABLE: git_utils.ChannelBranch(
remote="cros",
release_number=122,
branch_name="release-R122-15753.B",
),
},
)
class ShowFileAtRevTest(test_helpers.TempDirTestCase):
"""Class for testing the show-file-at-rev functionality.
This is tested against git since it has heuristics matching against git
output in error cases.
"""
def test_show_file_at_rev_works(self):
temp_dir = self.make_tempdir()
subprocess.run(
["git", "init"],
check=True,
cwd=temp_dir,
stdin=subprocess.DEVNULL,
)
(temp_dir / "foo").write_text("old text")
git_utils.commit_all_changes(temp_dir, message="commit 1")
(temp_dir / "foo").write_text("new text")
git_utils.commit_all_changes(temp_dir, message="commit 2")
# Test multiple cases here to avoid setting up multiple git dirs on
# every invocation of this test. They're reasonably self-contained
# anyway.
self.assertEqual(
git_utils.maybe_show_file_at_commit(temp_dir, "HEAD", "foo"),
"new text",
)
self.assertEqual(
git_utils.maybe_show_file_at_commit(temp_dir, "HEAD~", "foo"),
"old text",
)
self.assertIsNone(
git_utils.maybe_show_file_at_commit(temp_dir, "HEAD", "bar")
)
def test_show_dir_at_rev_works(self):
temp_dir = self.make_tempdir()
subprocess.run(
["git", "init"],
check=True,
cwd=temp_dir,
stdin=subprocess.DEVNULL,
)
(temp_dir / "file").write_text("foo")
git_utils.commit_all_changes(temp_dir, message="commit 1")
(temp_dir / "dir").mkdir()
(temp_dir / "dir" / "subfile1").touch()
git_utils.commit_all_changes(temp_dir, message="commit 2")
(temp_dir / "dir" / "subfile2").touch()
(temp_dir / "dir" / "subdir").mkdir()
(temp_dir / "dir" / "subdir" / "subfile3").touch()
git_utils.commit_all_changes(temp_dir, message="commit 3")
# Test multiple cases here to avoid setting up multiple git dirs on
# every invocation of this test. They're reasonably self-contained
# anyway.
self.assertIsNone(
git_utils.maybe_list_dir_contents_at_commit(
temp_dir, "HEAD~~", "dir"
),
)
self.assertEqual(
git_utils.maybe_list_dir_contents_at_commit(
temp_dir, "HEAD~", "dir"
),
["subfile1"],
)
self.assertEqual(
sorted(
git_utils.maybe_list_dir_contents_at_commit(
temp_dir, "HEAD", "dir"
)
),
["subdir/", "subfile1", "subfile2"],
)
with self.assertRaisesRegex(ValueError, ".*isn't a directory$"):
git_utils.maybe_list_dir_contents_at_commit(
temp_dir, "HEAD", "file"
)
class FormatPatchTest(test_helpers.TempDirTestCase):
"""Class for testing format_patch.
This is separated because it derives from TempDirTestCase,
which gives us nice temp directories.
"""
def setUp(self):
"""Set up the tests."""
# This cleans up automatically. No tearDown needed.
self.temp_dir = self.make_tempdir()
subprocess.run(
["git", "init"],
check=True,
cwd=self.temp_dir,
stdin=subprocess.DEVNULL,
)
self.foo_contents = "initial commit text"
(self.temp_dir / "foo").write_text(self.foo_contents, encoding="utf-8")
subject = "Initial commit"
git_utils.commit_all_changes(self.temp_dir, message=subject)
self.foo_contents = "here is special test text :)"
(self.temp_dir / "foo").write_text(self.foo_contents, encoding="utf-8")
subject = "Second commit"
git_utils.commit_all_changes(self.temp_dir, message=subject)
def test_format_patch(self):
"""Test that we can format patches correctly."""
formatted_patch = git_utils.format_patch(self.temp_dir, "HEAD")
self.assertIn(
"Subject: [PATCH] Second commit",
formatted_patch,
)
self.assertIn(
self.foo_contents,
formatted_patch,
)