1*3c875a21SAndroid Build Coastguard Worker# 2*3c875a21SAndroid Build Coastguard Worker# Copyright (C) 2023 The Android Open Source Project 3*3c875a21SAndroid Build Coastguard Worker# 4*3c875a21SAndroid Build Coastguard Worker# Licensed under the Apache License, Version 2.0 (the "License"); 5*3c875a21SAndroid Build Coastguard Worker# you may not use this file except in compliance with the License. 6*3c875a21SAndroid Build Coastguard Worker# You may obtain a copy of the License at 7*3c875a21SAndroid Build Coastguard Worker# 8*3c875a21SAndroid Build Coastguard Worker# http://www.apache.org/licenses/LICENSE-2.0 9*3c875a21SAndroid Build Coastguard Worker# 10*3c875a21SAndroid Build Coastguard Worker# Unless required by applicable law or agreed to in writing, software 11*3c875a21SAndroid Build Coastguard Worker# distributed under the License is distributed on an "AS IS" BASIS, 12*3c875a21SAndroid Build Coastguard Worker# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13*3c875a21SAndroid Build Coastguard Worker# See the License for the specific language governing permissions and 14*3c875a21SAndroid Build Coastguard Worker# limitations under the License. 15*3c875a21SAndroid Build Coastguard Worker# 16*3c875a21SAndroid Build Coastguard Worker"""Tests for gitrepo.""" 17*3c875a21SAndroid Build Coastguard Workerimport os 18*3c875a21SAndroid Build Coastguard Workerimport subprocess 19*3c875a21SAndroid Build Coastguard Workerimport unittest 20*3c875a21SAndroid Build Coastguard Workerfrom contextlib import ExitStack 21*3c875a21SAndroid Build Coastguard Workerfrom pathlib import Path 22*3c875a21SAndroid Build Coastguard Workerfrom tempfile import TemporaryDirectory 23*3c875a21SAndroid Build Coastguard Worker 24*3c875a21SAndroid Build Coastguard Workerfrom .gitrepo import GitRepo 25*3c875a21SAndroid Build Coastguard Worker 26*3c875a21SAndroid Build Coastguard Worker 27*3c875a21SAndroid Build Coastguard Workerclass GitRepoTest(unittest.TestCase): 28*3c875a21SAndroid Build Coastguard Worker """Tests for gitrepo.GitRepo.""" 29*3c875a21SAndroid Build Coastguard Worker 30*3c875a21SAndroid Build Coastguard Worker def setUp(self) -> None: 31*3c875a21SAndroid Build Coastguard Worker # Local test runs will probably pass without this since the caller 32*3c875a21SAndroid Build Coastguard Worker # almost certainly has git configured, but the bots that run the tests 33*3c875a21SAndroid Build Coastguard Worker # may not. **Do not** use `git config --global` for this, since that 34*3c875a21SAndroid Build Coastguard Worker # will modify the caller's config during local testing. 35*3c875a21SAndroid Build Coastguard Worker self._original_env = os.environ.copy() 36*3c875a21SAndroid Build Coastguard Worker os.environ["GIT_AUTHOR_NAME"] = "Testy McTestFace" 37*3c875a21SAndroid Build Coastguard Worker os.environ["GIT_AUTHOR_EMAIL"] = "[email protected]" 38*3c875a21SAndroid Build Coastguard Worker os.environ["GIT_COMMITTER_NAME"] = os.environ["GIT_AUTHOR_NAME"] 39*3c875a21SAndroid Build Coastguard Worker os.environ["GIT_COMMITTER_EMAIL"] = os.environ["GIT_AUTHOR_EMAIL"] 40*3c875a21SAndroid Build Coastguard Worker 41*3c875a21SAndroid Build Coastguard Worker with ExitStack() as stack: 42*3c875a21SAndroid Build Coastguard Worker temp_dir = TemporaryDirectory() # pylint: disable=consider-using-with 43*3c875a21SAndroid Build Coastguard Worker stack.enter_context(temp_dir) 44*3c875a21SAndroid Build Coastguard Worker self.addCleanup(stack.pop_all().close) 45*3c875a21SAndroid Build Coastguard Worker self.tmp_path = Path(temp_dir.name) 46*3c875a21SAndroid Build Coastguard Worker 47*3c875a21SAndroid Build Coastguard Worker def tearDown(self) -> None: 48*3c875a21SAndroid Build Coastguard Worker # This isn't trivially `os.environ = self._original_env` because 49*3c875a21SAndroid Build Coastguard Worker # os.environ isn't actually a dict, it's an os._Environ, and there isn't 50*3c875a21SAndroid Build Coastguard Worker # a good way to construct a new one of those. 51*3c875a21SAndroid Build Coastguard Worker os.environ.clear() 52*3c875a21SAndroid Build Coastguard Worker os.environ.update(self._original_env) 53*3c875a21SAndroid Build Coastguard Worker 54*3c875a21SAndroid Build Coastguard Worker def test_commit_adds_files(self) -> None: 55*3c875a21SAndroid Build Coastguard Worker """Tests that new files in commit are added to the repo.""" 56*3c875a21SAndroid Build Coastguard Worker repo = GitRepo(self.tmp_path / "repo") 57*3c875a21SAndroid Build Coastguard Worker repo.init() 58*3c875a21SAndroid Build Coastguard Worker repo.commit("Add README.md.", update_files={"README.md": "Hello, world!"}) 59*3c875a21SAndroid Build Coastguard Worker self.assertEqual(repo.commit_message_at_revision("HEAD"), "Add README.md.\n") 60*3c875a21SAndroid Build Coastguard Worker self.assertEqual( 61*3c875a21SAndroid Build Coastguard Worker repo.file_contents_at_revision("HEAD", "README.md"), "Hello, world!" 62*3c875a21SAndroid Build Coastguard Worker ) 63*3c875a21SAndroid Build Coastguard Worker 64*3c875a21SAndroid Build Coastguard Worker def test_commit_updates_files(self) -> None: 65*3c875a21SAndroid Build Coastguard Worker """Tests that updated files in commit are modified.""" 66*3c875a21SAndroid Build Coastguard Worker repo = GitRepo(self.tmp_path / "repo") 67*3c875a21SAndroid Build Coastguard Worker repo.init() 68*3c875a21SAndroid Build Coastguard Worker repo.commit("Add README.md.", update_files={"README.md": "Hello, world!"}) 69*3c875a21SAndroid Build Coastguard Worker repo.commit("Update README.md.", update_files={"README.md": "Goodbye, world!"}) 70*3c875a21SAndroid Build Coastguard Worker self.assertEqual(repo.commit_message_at_revision("HEAD^"), "Add README.md.\n") 71*3c875a21SAndroid Build Coastguard Worker self.assertEqual( 72*3c875a21SAndroid Build Coastguard Worker repo.file_contents_at_revision("HEAD^", "README.md"), "Hello, world!" 73*3c875a21SAndroid Build Coastguard Worker ) 74*3c875a21SAndroid Build Coastguard Worker self.assertEqual(repo.commit_message_at_revision("HEAD"), "Update README.md.\n") 75*3c875a21SAndroid Build Coastguard Worker self.assertEqual( 76*3c875a21SAndroid Build Coastguard Worker repo.file_contents_at_revision("HEAD", "README.md"), "Goodbye, world!" 77*3c875a21SAndroid Build Coastguard Worker ) 78*3c875a21SAndroid Build Coastguard Worker 79*3c875a21SAndroid Build Coastguard Worker def test_commit_deletes_files(self) -> None: 80*3c875a21SAndroid Build Coastguard Worker """Tests that files deleted by commit are removed from the repo.""" 81*3c875a21SAndroid Build Coastguard Worker repo = GitRepo(self.tmp_path / "repo") 82*3c875a21SAndroid Build Coastguard Worker repo.init() 83*3c875a21SAndroid Build Coastguard Worker repo.commit("Add README.md.", update_files={"README.md": "Hello, world!"}) 84*3c875a21SAndroid Build Coastguard Worker repo.commit("Remove README.md.", delete_files={"README.md"}) 85*3c875a21SAndroid Build Coastguard Worker self.assertEqual(repo.commit_message_at_revision("HEAD^"), "Add README.md.\n") 86*3c875a21SAndroid Build Coastguard Worker self.assertEqual( 87*3c875a21SAndroid Build Coastguard Worker repo.file_contents_at_revision("HEAD^", "README.md"), "Hello, world!" 88*3c875a21SAndroid Build Coastguard Worker ) 89*3c875a21SAndroid Build Coastguard Worker self.assertEqual(repo.commit_message_at_revision("HEAD"), "Remove README.md.\n") 90*3c875a21SAndroid Build Coastguard Worker self.assertNotEqual( 91*3c875a21SAndroid Build Coastguard Worker subprocess.run( 92*3c875a21SAndroid Build Coastguard Worker [ 93*3c875a21SAndroid Build Coastguard Worker "git", 94*3c875a21SAndroid Build Coastguard Worker "-C", 95*3c875a21SAndroid Build Coastguard Worker str(repo.path), 96*3c875a21SAndroid Build Coastguard Worker "ls-files", 97*3c875a21SAndroid Build Coastguard Worker "--error-unmatch", 98*3c875a21SAndroid Build Coastguard Worker "README.md", 99*3c875a21SAndroid Build Coastguard Worker ], 100*3c875a21SAndroid Build Coastguard Worker # The atest runner cannot parse test lines that have output. Hide the 101*3c875a21SAndroid Build Coastguard Worker # descriptive error from git (README.md does not exist, exactly what 102*3c875a21SAndroid Build Coastguard Worker # we're testing) so the test result can be parsed. 103*3c875a21SAndroid Build Coastguard Worker stderr=subprocess.DEVNULL, 104*3c875a21SAndroid Build Coastguard Worker check=False, 105*3c875a21SAndroid Build Coastguard Worker ).returncode, 106*3c875a21SAndroid Build Coastguard Worker 0, 107*3c875a21SAndroid Build Coastguard Worker ) 108*3c875a21SAndroid Build Coastguard Worker 109*3c875a21SAndroid Build Coastguard Worker def test_current_branch(self) -> None: 110*3c875a21SAndroid Build Coastguard Worker """Tests that current branch returns the current branch name.""" 111*3c875a21SAndroid Build Coastguard Worker repo = GitRepo(self.tmp_path / "repo") 112*3c875a21SAndroid Build Coastguard Worker repo.init("main") 113*3c875a21SAndroid Build Coastguard Worker self.assertEqual(repo.current_branch(), "main") 114*3c875a21SAndroid Build Coastguard Worker 115*3c875a21SAndroid Build Coastguard Worker def test_current_branch_fails_if_not_init(self) -> None: 116*3c875a21SAndroid Build Coastguard Worker """Tests that current branch fails when there is no git repo.""" 117*3c875a21SAndroid Build Coastguard Worker with self.assertRaises(subprocess.CalledProcessError): 118*3c875a21SAndroid Build Coastguard Worker GitRepo(self.tmp_path / "repo").current_branch() 119*3c875a21SAndroid Build Coastguard Worker 120*3c875a21SAndroid Build Coastguard Worker def test_switch_to_new_branch(self) -> None: 121*3c875a21SAndroid Build Coastguard Worker """Tests that switch_to_new_branch creates a new branch and switches to it.""" 122*3c875a21SAndroid Build Coastguard Worker repo = GitRepo(self.tmp_path / "repo") 123*3c875a21SAndroid Build Coastguard Worker repo.init("main") 124*3c875a21SAndroid Build Coastguard Worker repo.switch_to_new_branch("feature") 125*3c875a21SAndroid Build Coastguard Worker self.assertEqual(repo.current_branch(), "feature") 126*3c875a21SAndroid Build Coastguard Worker 127*3c875a21SAndroid Build Coastguard Worker def test_switch_to_new_branch_does_not_clobber_existing_branches(self) -> None: 128*3c875a21SAndroid Build Coastguard Worker """Tests that switch_to_new_branch raises an error for extant branches.""" 129*3c875a21SAndroid Build Coastguard Worker repo = GitRepo(self.tmp_path / "repo") 130*3c875a21SAndroid Build Coastguard Worker repo.init("main") 131*3c875a21SAndroid Build Coastguard Worker repo.commit("Initial commit.", allow_empty=True) 132*3c875a21SAndroid Build Coastguard Worker with self.assertRaises(subprocess.CalledProcessError): 133*3c875a21SAndroid Build Coastguard Worker repo.switch_to_new_branch("main") 134*3c875a21SAndroid Build Coastguard Worker 135*3c875a21SAndroid Build Coastguard Worker def test_switch_to_new_branch_with_start_point(self) -> None: 136*3c875a21SAndroid Build Coastguard Worker """Tests that switch_to_new_branch uses the provided start point.""" 137*3c875a21SAndroid Build Coastguard Worker repo = GitRepo(self.tmp_path / "repo") 138*3c875a21SAndroid Build Coastguard Worker repo.init("main") 139*3c875a21SAndroid Build Coastguard Worker repo.commit("Initial commit.", allow_empty=True) 140*3c875a21SAndroid Build Coastguard Worker initial_commit = repo.head() 141*3c875a21SAndroid Build Coastguard Worker repo.commit("Second commit.", allow_empty=True) 142*3c875a21SAndroid Build Coastguard Worker repo.switch_to_new_branch("feature", start_point=initial_commit) 143*3c875a21SAndroid Build Coastguard Worker self.assertEqual(repo.current_branch(), "feature") 144*3c875a21SAndroid Build Coastguard Worker self.assertEqual(repo.head(), initial_commit) 145*3c875a21SAndroid Build Coastguard Worker 146*3c875a21SAndroid Build Coastguard Worker def test_sha_of_ref(self) -> None: 147*3c875a21SAndroid Build Coastguard Worker """Tests that sha_of_ref returns the SHA of the given ref.""" 148*3c875a21SAndroid Build Coastguard Worker repo = GitRepo(self.tmp_path / "repo") 149*3c875a21SAndroid Build Coastguard Worker repo.init("main") 150*3c875a21SAndroid Build Coastguard Worker repo.commit("Initial commit.", allow_empty=True) 151*3c875a21SAndroid Build Coastguard Worker self.assertEqual(repo.sha_of_ref("heads/main"), repo.head()) 152*3c875a21SAndroid Build Coastguard Worker 153*3c875a21SAndroid Build Coastguard Worker def test_tag_head(self) -> None: 154*3c875a21SAndroid Build Coastguard Worker """Tests that tag creates a tag at HEAD.""" 155*3c875a21SAndroid Build Coastguard Worker repo = GitRepo(self.tmp_path / "repo") 156*3c875a21SAndroid Build Coastguard Worker repo.init() 157*3c875a21SAndroid Build Coastguard Worker repo.commit("Initial commit.", allow_empty=True) 158*3c875a21SAndroid Build Coastguard Worker repo.commit("Second commit.", allow_empty=True) 159*3c875a21SAndroid Build Coastguard Worker repo.tag("v1.0.0") 160*3c875a21SAndroid Build Coastguard Worker self.assertEqual(repo.sha_of_ref("tags/v1.0.0"), repo.head()) 161*3c875a21SAndroid Build Coastguard Worker 162*3c875a21SAndroid Build Coastguard Worker def test_tag_ref(self) -> None: 163*3c875a21SAndroid Build Coastguard Worker """Tests that tag creates a tag at the given ref.""" 164*3c875a21SAndroid Build Coastguard Worker repo = GitRepo(self.tmp_path / "repo") 165*3c875a21SAndroid Build Coastguard Worker repo.init() 166*3c875a21SAndroid Build Coastguard Worker repo.commit("Initial commit.", allow_empty=True) 167*3c875a21SAndroid Build Coastguard Worker first_commit = repo.head() 168*3c875a21SAndroid Build Coastguard Worker repo.commit("Second commit.", allow_empty=True) 169*3c875a21SAndroid Build Coastguard Worker repo.tag("v1.0.0", first_commit) 170*3c875a21SAndroid Build Coastguard Worker self.assertEqual(repo.sha_of_ref("tags/v1.0.0"), first_commit) 171*3c875a21SAndroid Build Coastguard Worker 172*3c875a21SAndroid Build Coastguard Worker 173*3c875a21SAndroid Build Coastguard Workerif __name__ == "__main__": 174*3c875a21SAndroid Build Coastguard Worker unittest.main(verbosity=2) 175