| #!/usr/bin/env python3 |
| # Copyright 2024 The Pigweed Authors |
| # |
| # Licensed under the Apache License, Version 2.0 (the "License"); you may not |
| # use this file except in compliance with the License. You may obtain a copy of |
| # the License at |
| # |
| # https://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
| # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
| # License for the specific language governing permissions and limitations under |
| # the License. |
| """git repo module tests""" |
| |
| from pathlib import Path |
| from subprocess import CompletedProcess |
| from typing import Dict |
| import re |
| import unittest |
| from unittest import mock |
| |
| from pw_cli.tool_runner import ToolRunner |
| from pw_cli.git_repo import GitRepo |
| |
| |
| class FakeGitToolRunner(ToolRunner): |
| def __init__(self, command_results: Dict[str, CompletedProcess]): |
| self._results = command_results |
| |
| def _run_tool(self, tool: str, args, **kwargs) -> CompletedProcess: |
| full_command = ' '.join((tool, *tuple(args))) |
| for cmd, result in self._results.items(): |
| if cmd in full_command: |
| return result |
| |
| return CompletedProcess( |
| args=full_command, |
| returncode=0xFF, |
| stderr=f'I do not know how to `{full_command}`'.encode(), |
| stdout=b'Failed to execute command', |
| ) |
| |
| |
| def git_ok(cmd: str, stdout: str) -> CompletedProcess: |
| return CompletedProcess( |
| args=cmd, |
| returncode=0, |
| stderr='', |
| stdout=stdout.encode(), |
| ) |
| |
| |
| def git_err(cmd: str, stderr: str, returncode: int = 1) -> CompletedProcess: |
| return CompletedProcess( |
| args=cmd, |
| returncode=returncode, |
| stderr=stderr.encode(), |
| stdout='', |
| ) |
| |
| |
| class TestGitRepo(unittest.TestCase): |
| """Tests for git_repo.py""" |
| |
| GIT_ROOT = Path("/dev/null/test").resolve() |
| SUBMODULES = [ |
| Path("/dev/null/test/third_party/pigweed").resolve(), |
| Path("/dev/null/test/vendor/anycom/p1").resolve(), |
| Path("/dev/null/test/vendor/anycom/p2").resolve(), |
| ] |
| GIT_SUBMODULES_OUT = "\n".join([str(x) for x in SUBMODULES]) |
| |
| EXPECTED_SUBMODULE_LIST_CMD = ' '.join( |
| ( |
| 'submodule', |
| 'foreach', |
| '--quiet', |
| '--recursive', |
| 'echo $toplevel/$sm_path', |
| ) |
| ) |
| |
| def make_fake_git_repo(self, cmds): |
| return GitRepo(self.GIT_ROOT, FakeGitToolRunner(cmds)) |
| |
| def test_mock_root(self): |
| """Ensure our mock works since so many of our tests depend upon it.""" |
| cmds = {} |
| repo = self.make_fake_git_repo(cmds) |
| self.assertEqual(repo.root(), self.GIT_ROOT) |
| |
| def test_list_submodules_1(self): |
| """Ensures the root git repo appears in the submodule list.""" |
| cmds = { |
| self.EXPECTED_SUBMODULE_LIST_CMD: git_ok( |
| self.EXPECTED_SUBMODULE_LIST_CMD, self.GIT_SUBMODULES_OUT |
| ) |
| } |
| repo = self.make_fake_git_repo(cmds) |
| paths = repo.list_submodules() |
| self.assertNotIn(self.GIT_ROOT, paths) |
| |
| def test_list_submodules_2(self): |
| cmds = { |
| self.EXPECTED_SUBMODULE_LIST_CMD: git_ok( |
| self.EXPECTED_SUBMODULE_LIST_CMD, self.GIT_SUBMODULES_OUT |
| ) |
| } |
| repo = self.make_fake_git_repo(cmds) |
| paths = repo.list_submodules() |
| self.assertIn(self.SUBMODULES[2], paths) |
| |
| def test_list_submodules_with_exclude_str(self): |
| cmds = { |
| self.EXPECTED_SUBMODULE_LIST_CMD: git_ok( |
| self.EXPECTED_SUBMODULE_LIST_CMD, self.GIT_SUBMODULES_OUT |
| ) |
| } |
| repo = self.make_fake_git_repo(cmds) |
| paths = repo.list_submodules( |
| excluded_paths=(self.GIT_ROOT.as_posix(),), |
| ) |
| self.assertNotIn(self.GIT_ROOT, paths) |
| |
| def test_list_submodules_with_exclude_regex(self): |
| cmds = { |
| self.EXPECTED_SUBMODULE_LIST_CMD: git_ok( |
| self.EXPECTED_SUBMODULE_LIST_CMD, self.GIT_SUBMODULES_OUT |
| ) |
| } |
| repo = self.make_fake_git_repo(cmds) |
| paths = repo.list_submodules( |
| excluded_paths=(re.compile("third_party/.*"),), |
| ) |
| self.assertNotIn(self.SUBMODULES[0], paths) |
| |
| def test_list_submodules_with_exclude_str_miss(self): |
| cmds = { |
| self.EXPECTED_SUBMODULE_LIST_CMD: git_ok( |
| self.EXPECTED_SUBMODULE_LIST_CMD, self.GIT_SUBMODULES_OUT |
| ) |
| } |
| repo = self.make_fake_git_repo(cmds) |
| paths = repo.list_submodules( |
| excluded_paths=(re.compile("pigweed"),), |
| ) |
| self.assertIn(self.SUBMODULES[-1], paths) |
| |
| def test_list_submodules_with_exclude_regex_miss_1(self): |
| cmds = { |
| self.EXPECTED_SUBMODULE_LIST_CMD: git_ok( |
| self.EXPECTED_SUBMODULE_LIST_CMD, self.GIT_SUBMODULES_OUT |
| ) |
| } |
| repo = self.make_fake_git_repo(cmds) |
| paths = repo.list_submodules( |
| excluded_paths=(re.compile("foo/.*"),), |
| ) |
| self.assertNotIn(self.GIT_ROOT, paths) |
| for module in self.SUBMODULES: |
| self.assertIn(module, paths) |
| |
| def test_list_submodules_with_exclude_regex_miss_2(self): |
| cmds = { |
| self.EXPECTED_SUBMODULE_LIST_CMD: git_ok( |
| self.EXPECTED_SUBMODULE_LIST_CMD, self.GIT_SUBMODULES_OUT |
| ) |
| } |
| repo = self.make_fake_git_repo(cmds) |
| paths = repo.list_submodules( |
| excluded_paths=(re.compile("pigweed"),), |
| ) |
| self.assertNotIn(self.GIT_ROOT, paths) |
| for module in self.SUBMODULES: |
| self.assertIn(module, paths) |
| |
| def test_list_files_unknown_hash(self): |
| bad_cmd = "diff --name-only --diff-filter=d 'something' --" |
| good_cmd = 'ls-files --' |
| fake_path = 'path/to/foo.h' |
| cmds = { |
| bad_cmd: git_err(bad_cmd, "fatal: bad revision 'something'"), |
| good_cmd: git_ok(good_cmd, fake_path + '\n'), |
| } |
| |
| expected_file_path = self.GIT_ROOT / Path(fake_path) |
| repo = self.make_fake_git_repo(cmds) |
| |
| # This function needs to be mocked because it does a `is_file()` check |
| # on returned paths. Since we're not using real files, nothing will |
| # be yielded. |
| repo._ls_files = mock.MagicMock( # pylint: disable=protected-access |
| return_value=[expected_file_path] |
| ) |
| paths = repo.list_files(commit='something') |
| self.assertIn(expected_file_path, paths) |
| |
| def test_fake_uncommitted_changes(self): |
| index_update = 'update-index -q --refresh' |
| diff_index = 'diff-index --quiet HEAD --' |
| cmds = { |
| index_update: git_ok(index_update, ''), |
| diff_index: git_err(diff_index, '', returncode=1), |
| } |
| repo = self.make_fake_git_repo(cmds) |
| self.assertTrue(repo.has_uncommitted_changes()) |
| |
| |
| if __name__ == '__main__': |
| unittest.main() |