# # 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"] = "test@example.com" 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)