| #!/usr/bin/env vpython3 |
| """Tests for split_cl.""" |
| |
| import os |
| import sys |
| import unittest |
| from unittest import mock |
| |
| sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) |
| |
| import split_cl |
| import gclient_utils |
| |
| |
| class SplitClTest(unittest.TestCase): |
| |
| @property |
| def _input_dir(self): |
| base = os.path.splitext(os.path.abspath(__file__))[0] |
| # Here _testMethodName is a string like "testCmdAssemblyFound" |
| # If the test doesn't have its own subdirectory, it uses a common one |
| path = os.path.join(base + ".inputs", self._testMethodName) |
| if not os.path.isdir(path): |
| path = os.path.join(base + ".inputs", "commonFiles") |
| return path |
| |
| def testAddUploadedByGitClSplitToDescription(self): |
| description = """Convert use of X to Y in $description |
| |
| <add some background about this conversion for the reviewers> |
| |
| """ |
| footers = 'Bug: 12345' |
| |
| added_line = 'This CL was uploaded by git cl split.' |
| experimental_lines = ("This CL was uploaded by an experimental version " |
| "of git cl split\n" |
| "(https://crbug.com/389069356).") |
| |
| # Description without footers |
| self.assertEqual( |
| split_cl.AddUploadedByGitClSplitToDescription(description), |
| description + added_line) |
| |
| # Description with footers |
| self.assertEqual( |
| split_cl.AddUploadedByGitClSplitToDescription(description + |
| footers), |
| description + added_line + '\n\n' + footers) |
| |
| # Description with footers and experimental flag |
| self.assertEqual( |
| split_cl.AddUploadedByGitClSplitToDescription( |
| description + footers, True), |
| description + experimental_lines + '\n\n' + footers) |
| |
| @mock.patch("split_cl.EmitWarning") |
| def testFormatDescriptionOrComment(self, mock_emit_warning): |
| description = "Converted use of X to Y in $description." |
| |
| # One directory |
| self.assertEqual( |
| split_cl.FormatDescriptionOrComment( |
| description, split_cl.FormatDirectoriesForPrinting(["foo"])), |
| "Converted use of X to Y in foo.", |
| ) |
| |
| # Many directories |
| self.assertEqual( |
| split_cl.FormatDescriptionOrComment( |
| description, |
| split_cl.FormatDirectoriesForPrinting(["foo", "bar"])), |
| "Converted use of X to Y in ['foo', 'bar'].", |
| ) |
| |
| mock_emit_warning.assert_not_called() |
| |
| description_deprecated = "Converted use of X to Y in $directory." |
| |
| # Make sure we emit a deprecation warning if the old format is used |
| self.assertEqual( |
| split_cl.FormatDescriptionOrComment( |
| description_deprecated, |
| split_cl.FormatDirectoriesForPrinting([])), |
| "Converted use of X to Y in [].", |
| ) |
| |
| mock_emit_warning.assert_called_once() |
| |
| def GetDirectoryBaseName(self, file_path): |
| return os.path.basename(os.path.dirname(file_path)) |
| |
| def MockSuggestOwners(self, paths, exclude=None): |
| if not paths: |
| return ["superowner"] |
| return self.GetDirectoryBaseName(paths[0]).split(",") |
| |
| def MockIsFile(self, file_path): |
| if os.path.basename(file_path) == "OWNERS": |
| return "owner" in self.GetDirectoryBaseName(file_path) |
| |
| return True |
| |
| @mock.patch("os.path.isfile") |
| def testSelectReviewersForFiles(self, mock_is_file): |
| mock_is_file.side_effect = self.MockIsFile |
| owners_client = mock.Mock(SuggestOwners=self.MockSuggestOwners, |
| EVERYONE="*") |
| cl = mock.Mock(owners_client=owners_client) |
| |
| files = [("M", os.path.join("foo", "owner1,owner2", "a.txt")), |
| ("M", os.path.join("foo", "owner1,owner2", "b.txt")), |
| ("M", os.path.join("bar", "owner1,owner2", "c.txt")), |
| ("M", os.path.join("bax", "owner2", "d.txt")), |
| ("M", os.path.join("baz", "owner3", "e.txt"))] |
| |
| files_split_by_reviewers = split_cl.SelectReviewersForFiles( |
| cl, "author", files, 0, "") |
| |
| self.assertEqual(3, len(files_split_by_reviewers.keys())) |
| info1 = files_split_by_reviewers[tuple(["owner1", "owner2"])] |
| self.assertEqual(info1.files, |
| [("M", os.path.join("foo", "owner1,owner2", "a.txt")), |
| ("M", os.path.join("foo", "owner1,owner2", "b.txt")), |
| ("M", os.path.join("bar", "owner1,owner2", "c.txt"))]) |
| self.assertEqual(info1.owners_directories, |
| ["foo/owner1,owner2", "bar/owner1,owner2"]) |
| info2 = files_split_by_reviewers[tuple(["owner2"])] |
| self.assertEqual(info2.files, |
| [("M", os.path.join("bax", "owner2", "d.txt"))]) |
| self.assertEqual(info2.owners_directories, ["bax/owner2"]) |
| info3 = files_split_by_reviewers[tuple(["owner3"])] |
| self.assertEqual(info3.files, |
| [("M", os.path.join("baz", "owner3", "e.txt"))]) |
| self.assertEqual(info3.owners_directories, ["baz/owner3"]) |
| |
| class UploadClTester: |
| """Sets up test environment for testing split_cl.UploadCl()""" |
| |
| def __init__(self, test): |
| self.mock_git_branches = self.StartPatcher("git_common.branches", |
| test) |
| self.mock_git_branches.return_value = [] |
| self.mock_git_current_branch = self.StartPatcher( |
| "git_common.current_branch", test) |
| self.mock_git_current_branch.return_value = "branch_to_upload" |
| self.mock_git_run = self.StartPatcher("git_common.run", test) |
| self.mock_temporary_file = self.StartPatcher( |
| "gclient_utils.temporary_file", test) |
| self.mock_temporary_file( |
| ).__enter__.return_value = "temporary_file0" |
| self.mock_file_writer = self.StartPatcher("gclient_utils.FileWrite", |
| test) |
| |
| def StartPatcher(self, target, test): |
| patcher = mock.patch(target) |
| test.addCleanup(patcher.stop) |
| return patcher.start() |
| |
| def DoUploadCl(self, description, files, reviewers, cmd_upload): |
| split_cl.UploadCl("branch_to_upload", "upstream_branch", |
| description, files, "description", |
| "splitting_file.txt", None, reviewers, |
| mock.Mock(), cmd_upload, True, True, "topic", |
| os.path.sep) |
| |
| def testUploadCl(self): |
| """Tests commands run by UploadCl.""" |
| |
| upload_cl_tester = self.UploadClTester(self) |
| |
| description = split_cl.FormatDirectoriesForPrinting(["dir0"]) |
| files = [("M", os.path.join("bar", "a.cc")), |
| ("D", os.path.join("foo", "b.cc"))] |
| reviewers = {"reviewer1@gmail.com", "reviewer2@gmail.com"} |
| mock_cmd_upload = mock.Mock() |
| upload_cl_tester.DoUploadCl(description, files, reviewers, |
| mock_cmd_upload) |
| |
| abs_repository_path = os.path.abspath(os.path.sep) |
| mock_git_run = upload_cl_tester.mock_git_run |
| self.assertEqual(mock_git_run.call_count, 4) |
| mock_git_run.assert_has_calls([ |
| mock.call("checkout", "-t", "upstream_branch", "-b", |
| split_cl.CreateBranchName("branch_to_upload", files)), |
| mock.call("rm", os.path.join(abs_repository_path, "foo", "b.cc")), |
| mock.call("checkout", "branch_to_upload", "--", |
| os.path.join(abs_repository_path, "bar", "a.cc")), |
| mock.call("commit", "-F", "temporary_file0") |
| ]) |
| |
| expected_upload_args = [ |
| "-f", "-r", "reviewer1@gmail.com,reviewer2@gmail.com", |
| "--cq-dry-run", "--send-mail", "--enable-auto-submit", |
| "--topic=topic" |
| ] |
| mock_cmd_upload.assert_called_once_with(expected_upload_args) |
| |
| def testDontUploadClIfBranchAlreadyExists(self): |
| """Tests that a CL is not uploaded if split branch already exists""" |
| |
| upload_cl_tester = self.UploadClTester(self) |
| |
| description = split_cl.FormatDirectoriesForPrinting(["dir0"]) |
| files = [("M", os.path.join("bar", "a.cc")), |
| ("D", os.path.join("foo", "b.cc"))] |
| reviewers = {"reviewer1@gmail.com"} |
| mock_cmd_upload = mock.Mock() |
| upload_cl_tester.mock_git_branches.return_value = [ |
| "branch0", |
| split_cl.CreateBranchName("branch_to_upload", files) |
| ] |
| upload_cl_tester.DoUploadCl(description, files, reviewers, |
| mock_cmd_upload) |
| |
| upload_cl_tester.mock_git_run.assert_not_called() |
| mock_cmd_upload.assert_not_called() |
| |
| @mock.patch("gclient_utils.AskForData") |
| def testCheckDescriptionBugLink(self, mock_ask_for_data): |
| # Description contains bug link. |
| self.assertTrue(split_cl.CheckDescriptionBugLink("Bug:1234")) |
| self.assertEqual(mock_ask_for_data.call_count, 0) |
| |
| # Description does not contain bug link. User does not enter 'y' when |
| # prompted. |
| mock_ask_for_data.reset_mock() |
| mock_ask_for_data.return_value = "m" |
| self.assertFalse(split_cl.CheckDescriptionBugLink("Description")) |
| self.assertEqual(mock_ask_for_data.call_count, 1) |
| |
| # Description does not contain bug link. User enters 'y' when prompted. |
| mock_ask_for_data.reset_mock() |
| mock_ask_for_data.return_value = "y" |
| self.assertTrue(split_cl.CheckDescriptionBugLink("Description")) |
| self.assertEqual(mock_ask_for_data.call_count, 1) |
| |
| @mock.patch("gclient_utils.FileRead", return_value="Description") |
| def testLoadDescription(self, mock_file_read): |
| # No description provided, use the dummy: |
| self.assertTrue( |
| split_cl.LoadDescription(None, True).startswith("Dummy")) |
| self.assertEqual(mock_file_read.call_count, 0) |
| |
| # No description provided during a real run |
| self.assertRaises(ValueError, split_cl.LoadDescription, None, False) |
| self.assertEqual(mock_file_read.call_count, 0) |
| |
| # Description file provided, load it regardless of dry run |
| self.assertEqual(split_cl.LoadDescription("SomeFile.txt", False), |
| "Description") |
| self.assertEqual(mock_file_read.call_count, 1) |
| |
| mock_file_read.reset_mock() |
| self.assertEqual(split_cl.LoadDescription("SomeFile.txt", True), |
| "Description") |
| self.assertEqual(mock_file_read.call_count, 1) |
| |
| class SplitClTester: |
| """Sets up test environment for testing split_cl.SplitCl()""" |
| |
| def __init__(self, test): |
| self.mocks = [] |
| self.mock_file_read = self.StartPatcher( |
| "gclient_utils.FileRead", |
| test, |
| return_value="Non-dummy description\nBug: 1243") |
| self.mock_in_git_repo = self.StartPatcher( |
| "split_cl.EnsureInGitRepository", test) |
| self.mock_git_status = self.StartPatcher("scm.GIT.CaptureStatus", |
| test) |
| self.mock_git_run = self.StartPatcher("git_common.run", test) |
| self.mock_git_current_branch = self.StartPatcher( |
| "git_common.current_branch", |
| test, |
| return_value="branch_to_upload") |
| self.mock_git_branches = self.StartPatcher("git_common.branches", |
| test) |
| self.mock_git_upstream = self.StartPatcher( |
| "git_common.upstream", test, return_value="upstream_branch") |
| self.mock_get_reviewers = self.StartPatcher( |
| "split_cl.SelectReviewersForFiles", test) |
| self.mock_ask_for_data = self.StartPatcher( |
| "gclient_utils.AskForData", test) |
| self.mock_print_cl_info = self.StartPatcher("split_cl.PrintClInfo", |
| test) |
| self.mock_print_summary = self.StartPatcher("split_cl.PrintSummary", |
| test) |
| self.mock_upload_cl = self.StartPatcher("split_cl.UploadCl", test) |
| self.mock_save_splitting = self.StartPatcher( |
| "split_cl.SaveSplittingToTempFile", test) |
| # Suppress output for cleaner tests |
| self.mock_emit = self.StartPatcher("split_cl.Emit", test) |
| |
| def StartPatcher(self, target, test, **kwargs): |
| patcher = mock.patch(target, **kwargs) |
| test.addCleanup(patcher.stop) |
| m = patcher.start() |
| self.mocks.append(m) |
| return m |
| |
| def ResetMocks(self): |
| for m in self.mocks: |
| m.reset_mock() |
| |
| def DoSplitCl(self, description_file, dry_run, summarize, |
| reviewers_override, files_split_by_reviewers, |
| proceed_response): |
| all_files = [v.files for v in files_split_by_reviewers.values()] |
| all_files_flattened = [ |
| file for files in all_files for file in files |
| ] |
| |
| self.mock_git_status.return_value = all_files_flattened |
| self.mock_get_reviewers.return_value = files_split_by_reviewers |
| self.mock_ask_for_data.return_value = proceed_response |
| |
| split_cl.SplitCl(description_file, None, mock.Mock(), mock.Mock(), |
| dry_run, summarize, reviewers_override, False, |
| False, None, None, None, None, None, None) |
| |
| # Save for re-use |
| files_split_by_reviewers = { |
| ("a@example.com", ): |
| split_cl.FilesAndOwnersDirectory([ |
| ("M", "a/b/foo.cc"), |
| ("M", "d/e/bar.h"), |
| ], []), |
| ("b@example.com", ): |
| split_cl.FilesAndOwnersDirectory([ |
| ("A", "f/g/baz.py"), |
| ], []) |
| } |
| |
| def testSplitClConfirm(self): |
| split_cl_tester = self.SplitClTester(self) |
| |
| # Should prompt for confirmation and upload several times |
| split_cl_tester.DoSplitCl("SomeFile.txt", False, False, None, |
| self.files_split_by_reviewers, "y") |
| |
| split_cl_tester.mock_ask_for_data.assert_called_once() |
| split_cl_tester.mock_print_cl_info.assert_not_called() |
| self.assertEqual(split_cl_tester.mock_upload_cl.call_count, |
| len(self.files_split_by_reviewers)) |
| |
| split_cl_tester.ResetMocks() |
| # Should prompt for confirmation and not upload |
| split_cl_tester.DoSplitCl("SomeFile.txt", False, False, None, |
| self.files_split_by_reviewers, "f") |
| |
| split_cl_tester.mock_ask_for_data.assert_called_once() |
| split_cl_tester.mock_print_cl_info.assert_not_called() |
| split_cl_tester.mock_upload_cl.assert_not_called() |
| |
| split_cl_tester.ResetMocks() |
| |
| # Dry runs: Don't prompt, print info instead of uploading |
| split_cl_tester.DoSplitCl("SomeFile.txt", True, False, None, |
| self.files_split_by_reviewers, "f") |
| |
| split_cl_tester.mock_ask_for_data.assert_not_called() |
| self.assertEqual(split_cl_tester.mock_print_cl_info.call_count, |
| len(self.files_split_by_reviewers)) |
| split_cl_tester.mock_print_summary.assert_not_called() |
| split_cl_tester.mock_upload_cl.assert_not_called() |
| |
| split_cl_tester.ResetMocks() |
| # Summarize is true: Don't prompt, emit a summary |
| split_cl_tester.DoSplitCl("SomeFile.txt", True, True, None, |
| self.files_split_by_reviewers, "f") |
| |
| split_cl_tester.mock_ask_for_data.assert_not_called() |
| split_cl_tester.mock_print_cl_info.assert_not_called() |
| split_cl_tester.mock_print_summary.assert_called_once() |
| split_cl_tester.mock_upload_cl.assert_not_called() |
| |
| def testReviewerOverride(self): |
| split_cl_tester = self.SplitClTester(self) |
| |
| def testOneOverride(reviewers_lst): |
| split_cl_tester.DoSplitCl("SomeFile.txt", False, False, |
| reviewers_lst, |
| self.files_split_by_reviewers, "y") |
| |
| for call in split_cl_tester.mock_upload_cl.call_args_list: |
| self.assertEqual(call.args[7], set(reviewers_lst)) |
| |
| split_cl_tester.ResetMocks() |
| |
| # The 'None' case gets ample testing everywhere else |
| testOneOverride([]) |
| testOneOverride(['a@b.com', 'c@d.com']) |
| |
| def testValidateExistingBranches(self): |
| """ |
| Make sure that we skip existing branches if they match what we intend |
| to do, and fail if there are existing branches that don't match. |
| """ |
| |
| split_cl_tester = self.SplitClTester(self) |
| |
| # If no split branches exist, we should call upload once per CL |
| split_cl_tester.mock_git_branches.return_value = [ |
| "branch0", "branch_to_upload" |
| ] |
| split_cl_tester.DoSplitCl("SomeFile.txt", False, False, None, |
| self.files_split_by_reviewers, "y") |
| self.assertEqual(split_cl_tester.mock_upload_cl.call_count, |
| len(self.files_split_by_reviewers)) |
| |
| # TODO(389069356): We should also ensure that if there are existing |
| # branches that match our current splitting, we skip them when uploading |
| # Unfortunately, we're not set up to test that, so this will have to |
| # wait until we've refactored SplitCl and UploadCL to be less |
| # monolithic |
| |
| # If a split branch with a bad name already exists, we should fail |
| split_cl_tester.mock_upload_cl.reset_mock() |
| split_cl_tester.mock_git_branches.return_value = [ |
| "branch0", "branch_to_upload", |
| "branch_to_upload_123456789_whatever_split" |
| ] |
| split_cl_tester.DoSplitCl("SomeFile.txt", False, False, None, |
| self.files_split_by_reviewers, "y") |
| split_cl_tester.mock_upload_cl.assert_not_called() |
| |
| |
| # Tests related to saving to and loading from files |
| # Sample CLInfos for testing |
| CLInfo_1 = split_cl.CLInfo(reviewers=["a@example.com"], |
| description="['chrome/browser']", |
| files=[ |
| ("M", "chrome/browser/a.cc"), |
| ("M", "chrome/browser/b.cc"), |
| ]) |
| |
| CLInfo_2 = split_cl.CLInfo(reviewers=["a@example.com", "b@example.com"], |
| description="['foo', 'bar/baz']", |
| files=[("M", "foo/browser/a.cc"), |
| ("M", "bar/baz/b.cc"), |
| ("D", "foo/bar/c.h")]) |
| |
| def testCLInfoFormat(self): |
| """ Make sure CLInfo printing works as expected """ |
| |
| def ReadAndStripPreamble(file): |
| """ Read the contents of a file and strip the automatically-added |
| preamble so we can do string comparison |
| """ |
| content = gclient_utils.FileRead(os.path.join( |
| self._input_dir, file)) |
| # Strip preamble |
| stripped = [ |
| line for line in content.splitlines() |
| if not line.startswith("#") |
| ] |
| # Strip newlines in preamble |
| return "\n".join(stripped[2:]) |
| |
| # Direct string comparison |
| self.assertEqual(self.CLInfo_1.FormatForPrinting(), |
| ReadAndStripPreamble("1_cl.txt")) |
| |
| self.assertEqual( |
| self.CLInfo_1.FormatForPrinting() + |
| "\n\n" + self.CLInfo_2.FormatForPrinting(), |
| ReadAndStripPreamble("2_cls.txt")) |
| |
| @mock.patch("split_cl.EmitWarning") |
| def testParseCLInfo(self, mock_emit_warning): |
| """ Make sure we can parse valid files """ |
| |
| self.assertEqual([self.CLInfo_1], |
| split_cl.LoadSplittingFromFile( |
| os.path.join(self._input_dir, "1_cl.txt"), |
| self.CLInfo_1.files)) |
| self.assertEqual([self.CLInfo_1, self.CLInfo_2], |
| split_cl.LoadSplittingFromFile( |
| os.path.join(self._input_dir, "2_cls.txt"), |
| self.CLInfo_1.files + self.CLInfo_2.files)) |
| |
| # Make sure everything in this file is valid to parse |
| split_cl.LoadSplittingFromFile( |
| os.path.join(self._input_dir, "odd_formatting.txt"), |
| self.CLInfo_1.files + self.CLInfo_2.files + [("A", "a/b/c"), |
| ("A", "a/b/d"), |
| ("D", "a/e")]) |
| mock_emit_warning.assert_not_called() |
| |
| def testParseBadFiles(self): |
| """ Make sure we don't parse invalid files """ |
| for file in os.listdir(self._input_dir): |
| lines = gclient_utils.FileRead(os.path.join(self._input_dir, |
| file)).splitlines() |
| self.assertRaises(split_cl.ClSplitParseError, |
| split_cl.ParseSplittings, lines) |
| |
| @mock.patch("split_cl.EmitWarning") |
| @mock.patch("split_cl.Emit") |
| def testValidateBadFiles(self, _, mock_emit_warning): |
| """ Make sure we reject invalid CL lists """ |
| # Warn on an empty file |
| split_cl.LoadSplittingFromFile( |
| os.path.join(self._input_dir, "warn_0_cls.txt"), []) |
| mock_emit_warning.assert_called_once() |
| mock_emit_warning.reset_mock() |
| |
| # Warn if reviewers don't look like emails |
| split_cl.LoadSplittingFromFile( |
| os.path.join(self._input_dir, "warn_bad_reviewer_email.txt"), |
| [("M", "a.cc")]) |
| self.assertEqual(mock_emit_warning.call_count, 2) |
| mock_emit_warning.reset_mock() |
| |
| # Fail if a file appears in multiple CLs |
| self.assertRaises( |
| split_cl.ClSplitParseError, split_cl.LoadSplittingFromFile, |
| os.path.join(self._input_dir, "error_file_in_multiple_cls.txt"), |
| [("M", "chrome/browser/a.cc"), ("M", "chrome/browser/b.cc"), |
| ("M", "bar/baz/b.cc"), ("D", "foo/bar/c.h")]) |
| |
| # Fail if a file is listed that doesn't appear on disk |
| self.assertRaises( |
| split_cl.ClSplitParseError, split_cl.LoadSplittingFromFile, |
| os.path.join(self._input_dir, "no_inherent_problems.txt"), |
| [("M", "chrome/browser/a.cc"), ("M", "chrome/browser/b.cc")]) |
| self.assertRaises( |
| split_cl.ClSplitParseError, |
| split_cl.LoadSplittingFromFile, |
| os.path.join(self._input_dir, "no_inherent_problems.txt"), |
| [ |
| ("M", "chrome/browser/a.cc"), |
| ("M", "chrome/browser/b.cc"), |
| ("D", "c.h") # Wrong action, should still error |
| ]) |
| |
| # Warn if not all files on disk are included |
| split_cl.LoadSplittingFromFile( |
| os.path.join(self._input_dir, "no_inherent_problems.txt"), |
| [("M", "chrome/browser/a.cc"), ("M", "chrome/browser/b.cc"), |
| ("A", "c.h"), ("D", "d.h")]) |
| mock_emit_warning.assert_called_once() |
| |
| @mock.patch("split_cl.Emit") |
| @mock.patch("gclient_utils.FileWrite") |
| def testParsingRoundTrip(self, mock_file_write, _): |
| """ Make sure that if we parse a file and save the result, |
| we get the same file. Only works on test files that are |
| nicely formatted. """ |
| |
| for file in os.listdir(self._input_dir): |
| if file == "odd_formatting.txt": |
| continue |
| contents = gclient_utils.FileRead( |
| os.path.join(self._input_dir, file)) |
| parsed_contents = split_cl.ParseSplittings(contents.splitlines()) |
| split_cl.SaveSplittingToFile(parsed_contents, "file.txt") |
| |
| written_lines = [ |
| args[0][1] for args in mock_file_write.call_args_list |
| ] |
| |
| self.assertEqual(contents, "".join(written_lines)) |
| mock_file_write.reset_mock() |
| |
| @mock.patch("os.path.isfile", return_value=False) |
| def testDirectoryTrie(self, _): |
| """ |
| Simple unit tests for creating and reading from a DirectoryTrie. |
| """ |
| # The trie code uses OS paths so we need to do the same here |
| path_abc = os.path.join("a", "b", "c.cc") |
| path_abd = os.path.join("a", "b", "d.h") |
| path_aefgh = os.path.join("a", "e", "f", "g", "h.hpp") |
| path_ijk = os.path.join("i", "j", "k.cc") |
| path_al = os.path.join("a", "l.cpp") |
| path_top = os.path.join("top.gn") |
| |
| files = [path_abc, path_abd, path_aefgh, path_ijk, path_al, path_top] |
| split_files = [file.split(os.path.sep) for file in files] |
| |
| trie = split_cl.DirectoryTrie(False) |
| trie.AddFiles(split_files) |
| |
| self.assertEqual(trie.files, [path_top]) |
| self.assertEqual(trie.subdirectories["a"].files, [path_al]) |
| self.assertEqual(trie.subdirectories["a"].subdirectories["b"].files, |
| [path_abc, path_abd]) |
| self.assertEqual(sorted(trie.ToList()), sorted(files)) |
| |
| self.assertFalse(trie.has_parent) |
| self.assertFalse(trie.subdirectories["a"].has_parent) |
| self.assertTrue(trie.subdirectories["a"].subdirectories["b"].has_parent) |
| |
| self.assertEqual(trie.prefix, "") |
| self.assertEqual(trie.subdirectories["a"].prefix, "a") |
| self.assertEqual(trie.subdirectories["a"].subdirectories["b"].prefix, |
| os.path.join("a", "b")) |
| |
| @mock.patch("os.path.isfile", return_value=False) |
| def testClusterFiles(self, _): |
| """ |
| Make sure ClusterFiles returns sensible results for some sample inputs. |
| """ |
| |
| def compareClusterOutput(clusters: list[split_cl.Bin], |
| file_groups: list[list[str]]): |
| """ |
| Ensure that ClusterFiles grouped files the way we expected it to. |
| """ |
| clustered_files = sorted([sorted(bin.files) for bin in clusters]) |
| file_groups = sorted([sorted(grp) for grp in file_groups]) |
| self.assertEqual(clustered_files, file_groups) |
| |
| # The clustering code uses OS paths so we need to do the same here |
| path_abc = os.path.join("a", "b", "c.cc") |
| path_abd = os.path.join("a", "b", "d.h") |
| path_aefgh = os.path.join("a", "e", "f", "g", "h.hpp") |
| path_ijk = os.path.join("i", "j", "k.cc") |
| path_ilm = os.path.join("i", "l", "m.cc") |
| path_an = os.path.join("a", "n.cpp") |
| path_top = os.path.join("top.gn") |
| files = [ |
| path_abc, path_abd, path_aefgh, path_ijk, path_ilm, path_an, |
| path_top |
| ] |
| |
| def checkClustering(min_files, max_files, expected): |
| clusters = split_cl.ClusterFiles(False, files, min_files, max_files) |
| compareClusterOutput(clusters, expected) |
| |
| # Each file gets its own cluster |
| individual_files = [[file] for file in files] |
| checkClustering(1, 1, individual_files) |
| |
| # Put both entries of a/b in the same cluster, everything else alone |
| ab_together = [[path_abc, path_abd], [path_aefgh], [path_ijk], |
| [path_ilm], [path_an], [path_top]] |
| checkClustering(1, 2, ab_together) |
| checkClustering(1, 100, ab_together) |
| |
| # Groups of 2: a/b, rest of a/, all of i/. |
| a_two_groups = [[path_abc, path_abd], [path_aefgh, path_an], |
| [path_ijk, path_ilm], [path_top]] |
| checkClustering(2, 2, a_two_groups) |
| checkClustering(3, 3, a_two_groups) |
| |
| # Put all of a/ together and all of i/ together. |
| # Don't combine top-level directories with things at the root |
| by_top_level_dir = [[path_abc, path_abd, path_aefgh, path_an], |
| [path_ijk, path_ilm], [path_top]] |
| checkClustering(3, 5, by_top_level_dir) |
| checkClustering(100, 200, by_top_level_dir) |
| |
| if __name__ == '__main__': |
| unittest.main() |