xref: /aosp_15_r20/tools/external_updater/tests/test_gitrepo.py (revision 3c875a214f382db1236d28570d1304ce57138f32)
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