| # | |
| # Copyright (C) 2023 The Android Open Source Project | |
| # | |
| # 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 | |
| # | |
| # http://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. | |
| # | |
| """Tests for gitrepo.""" | |
| import os | |
| import subprocess | |
| import unittest | |
| from contextlib import ExitStack | |
| from pathlib import Path | |
| from tempfile import TemporaryDirectory | |
| from .gitrepo import GitRepo | |
| class GitRepoTest(unittest.TestCase): | |
| """Tests for gitrepo.GitRepo.""" | |
| def setUp(self) -> None: | |
| # Local test runs will probably pass without this since the caller | |
| # almost certainly has git configured, but the bots that run the tests | |
| # may not. **Do not** use `git config --global` for this, since that | |
| # will modify the caller's config during local testing. | |
| self._original_env = os.environ.copy() | |
| os.environ["GIT_AUTHOR_NAME"] = "Testy McTestFace" | |
| os.environ["GIT_AUTHOR_EMAIL"] = "[email protected]" | |
| os.environ["GIT_COMMITTER_NAME"] = os.environ["GIT_AUTHOR_NAME"] | |
| os.environ["GIT_COMMITTER_EMAIL"] = os.environ["GIT_AUTHOR_EMAIL"] | |
| with ExitStack() as stack: | |
| temp_dir = TemporaryDirectory() # pylint: disable=consider-using-with | |
| stack.enter_context(temp_dir) | |
| self.addCleanup(stack.pop_all().close) | |
| self.tmp_path = Path(temp_dir.name) | |
| def tearDown(self) -> None: | |
| # This isn't trivially `os.environ = self._original_env` because | |
| # os.environ isn't actually a dict, it's an os._Environ, and there isn't | |
| # a good way to construct a new one of those. | |
| os.environ.clear() | |
| os.environ.update(self._original_env) | |
| def test_commit_adds_files(self) -> None: | |
| """Tests that new files in commit are added to the repo.""" | |
| repo = GitRepo(self.tmp_path / "repo") | |
| repo.init() | |
| repo.commit("Add README.md.", update_files={"README.md": "Hello, world!"}) | |
| self.assertEqual(repo.commit_message_at_revision("HEAD"), "Add README.md.\n") | |
| self.assertEqual( | |
| repo.file_contents_at_revision("HEAD", "README.md"), "Hello, world!" | |
| ) | |
| def test_commit_updates_files(self) -> None: | |
| """Tests that updated files in commit are modified.""" | |
| repo = GitRepo(self.tmp_path / "repo") | |
| repo.init() | |
| repo.commit("Add README.md.", update_files={"README.md": "Hello, world!"}) | |
| repo.commit("Update README.md.", update_files={"README.md": "Goodbye, world!"}) | |
| self.assertEqual(repo.commit_message_at_revision("HEAD^"), "Add README.md.\n") | |
| self.assertEqual( | |
| repo.file_contents_at_revision("HEAD^", "README.md"), "Hello, world!" | |
| ) | |
| self.assertEqual(repo.commit_message_at_revision("HEAD"), "Update README.md.\n") | |
| self.assertEqual( | |
| repo.file_contents_at_revision("HEAD", "README.md"), "Goodbye, world!" | |
| ) | |
| def test_commit_deletes_files(self) -> None: | |
| """Tests that files deleted by commit are removed from the repo.""" | |
| repo = GitRepo(self.tmp_path / "repo") | |
| repo.init() | |
| repo.commit("Add README.md.", update_files={"README.md": "Hello, world!"}) | |
| repo.commit("Remove README.md.", delete_files={"README.md"}) | |
| self.assertEqual(repo.commit_message_at_revision("HEAD^"), "Add README.md.\n") | |
| self.assertEqual( | |
| repo.file_contents_at_revision("HEAD^", "README.md"), "Hello, world!" | |
| ) | |
| self.assertEqual(repo.commit_message_at_revision("HEAD"), "Remove README.md.\n") | |
| self.assertNotEqual( | |
| subprocess.run( | |
| [ | |
| "git", | |
| "-C", | |
| str(repo.path), | |
| "ls-files", | |
| "--error-unmatch", | |
| "README.md", | |
| ], | |
| # The atest runner cannot parse test lines that have output. Hide the | |
| # descriptive error from git (README.md does not exist, exactly what | |
| # we're testing) so the test result can be parsed. | |
| stderr=subprocess.DEVNULL, | |
| check=False, | |
| ).returncode, | |
| 0, | |
| ) | |
| def test_current_branch(self) -> None: | |
| """Tests that current branch returns the current branch name.""" | |
| repo = GitRepo(self.tmp_path / "repo") | |
| repo.init("main") | |
| self.assertEqual(repo.current_branch(), "main") | |
| def test_current_branch_fails_if_not_init(self) -> None: | |
| """Tests that current branch fails when there is no git repo.""" | |
| with self.assertRaises(subprocess.CalledProcessError): | |
| GitRepo(self.tmp_path / "repo").current_branch() | |
| def test_switch_to_new_branch(self) -> None: | |
| """Tests that switch_to_new_branch creates a new branch and switches to it.""" | |
| repo = GitRepo(self.tmp_path / "repo") | |
| repo.init("main") | |
| repo.switch_to_new_branch("feature") | |
| self.assertEqual(repo.current_branch(), "feature") | |
| def test_switch_to_new_branch_does_not_clobber_existing_branches(self) -> None: | |
| """Tests that switch_to_new_branch raises an error for extant branches.""" | |
| repo = GitRepo(self.tmp_path / "repo") | |
| repo.init("main") | |
| repo.commit("Initial commit.", allow_empty=True) | |
| with self.assertRaises(subprocess.CalledProcessError): | |
| repo.switch_to_new_branch("main") | |
| def test_switch_to_new_branch_with_start_point(self) -> None: | |
| """Tests that switch_to_new_branch uses the provided start point.""" | |
| repo = GitRepo(self.tmp_path / "repo") | |
| repo.init("main") | |
| repo.commit("Initial commit.", allow_empty=True) | |
| initial_commit = repo.head() | |
| repo.commit("Second commit.", allow_empty=True) | |
| repo.switch_to_new_branch("feature", start_point=initial_commit) | |
| self.assertEqual(repo.current_branch(), "feature") | |
| self.assertEqual(repo.head(), initial_commit) | |
| def test_sha_of_ref(self) -> None: | |
| """Tests that sha_of_ref returns the SHA of the given ref.""" | |
| repo = GitRepo(self.tmp_path / "repo") | |
| repo.init("main") | |
| repo.commit("Initial commit.", allow_empty=True) | |
| self.assertEqual(repo.sha_of_ref("heads/main"), repo.head()) | |
| def test_tag_head(self) -> None: | |
| """Tests that tag creates a tag at HEAD.""" | |
| repo = GitRepo(self.tmp_path / "repo") | |
| repo.init() | |
| repo.commit("Initial commit.", allow_empty=True) | |
| repo.commit("Second commit.", allow_empty=True) | |
| repo.tag("v1.0.0") | |
| self.assertEqual(repo.sha_of_ref("tags/v1.0.0"), repo.head()) | |
| def test_tag_ref(self) -> None: | |
| """Tests that tag creates a tag at the given ref.""" | |
| repo = GitRepo(self.tmp_path / "repo") | |
| repo.init() | |
| repo.commit("Initial commit.", allow_empty=True) | |
| first_commit = repo.head() | |
| repo.commit("Second commit.", allow_empty=True) | |
| repo.tag("v1.0.0", first_commit) | |
| self.assertEqual(repo.sha_of_ref("tags/v1.0.0"), first_commit) | |
| if __name__ == "__main__": | |
| unittest.main(verbosity=2) |