Mike Frysinger | ca540ae | 2019-07-10 15:42:30 -0400 | [diff] [blame] | 1 | # Copyright 2019 The Android Open Source Project |
| 2 | # |
| 3 | # Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 | # you may not use this file except in compliance with the License. |
| 5 | # You may obtain a copy of the License at |
| 6 | # |
| 7 | # http://www.apache.org/licenses/LICENSE-2.0 |
| 8 | # |
| 9 | # Unless required by applicable law or agreed to in writing, software |
| 10 | # distributed under the License is distributed on an "AS IS" BASIS, |
| 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 | # See the License for the specific language governing permissions and |
| 13 | # limitations under the License. |
| 14 | |
Mike Frysinger | 87deaef | 2019-07-26 21:14:55 -0400 | [diff] [blame] | 15 | """Unittests for the git_command.py module.""" |
| 16 | |
Aravind Vasudevan | 2844a5f | 2023-10-06 00:40:25 +0000 | [diff] [blame] | 17 | import io |
Sam Saccone | d686365 | 2022-11-15 23:57:22 +0000 | [diff] [blame] | 18 | import os |
Mike Frysinger | 6447733 | 2023-08-21 21:20:32 -0400 | [diff] [blame] | 19 | import re |
Jason Chang | a6413f5 | 2023-07-26 13:23:40 -0700 | [diff] [blame] | 20 | import subprocess |
Mike Frysinger | ca540ae | 2019-07-10 15:42:30 -0400 | [diff] [blame] | 21 | import unittest |
Jason R. Coombs | b32ccbb | 2023-09-29 11:04:49 -0400 | [diff] [blame] | 22 | from unittest import mock |
Mike Frysinger | a1e24b1 | 2020-02-19 22:21:21 -0500 | [diff] [blame] | 23 | |
Mike Frysinger | ca540ae | 2019-07-10 15:42:30 -0400 | [diff] [blame] | 24 | import git_command |
Mike Frysinger | a1e24b1 | 2020-02-19 22:21:21 -0500 | [diff] [blame] | 25 | import wrapper |
Mike Frysinger | ca540ae | 2019-07-10 15:42:30 -0400 | [diff] [blame] | 26 | |
| 27 | |
Sam Saccone | d686365 | 2022-11-15 23:57:22 +0000 | [diff] [blame] | 28 | class GitCommandTest(unittest.TestCase): |
Gavin Mak | ea2e330 | 2023-03-11 06:46:20 +0000 | [diff] [blame] | 29 | """Tests the GitCommand class (via git_command.git).""" |
Sam Saccone | d686365 | 2022-11-15 23:57:22 +0000 | [diff] [blame] | 30 | |
Gavin Mak | ea2e330 | 2023-03-11 06:46:20 +0000 | [diff] [blame] | 31 | def setUp(self): |
| 32 | def realpath_mock(val): |
| 33 | return val |
Sam Saccone | d686365 | 2022-11-15 23:57:22 +0000 | [diff] [blame] | 34 | |
Gavin Mak | ea2e330 | 2023-03-11 06:46:20 +0000 | [diff] [blame] | 35 | mock.patch.object( |
| 36 | os.path, "realpath", side_effect=realpath_mock |
| 37 | ).start() |
Sam Saccone | d686365 | 2022-11-15 23:57:22 +0000 | [diff] [blame] | 38 | |
Gavin Mak | ea2e330 | 2023-03-11 06:46:20 +0000 | [diff] [blame] | 39 | def tearDown(self): |
| 40 | mock.patch.stopall() |
Sam Saccone | d686365 | 2022-11-15 23:57:22 +0000 | [diff] [blame] | 41 | |
Gavin Mak | ea2e330 | 2023-03-11 06:46:20 +0000 | [diff] [blame] | 42 | def test_alternative_setting_when_matching(self): |
| 43 | r = git_command._build_env( |
| 44 | objdir=os.path.join("zap", "objects"), gitdir="zap" |
| 45 | ) |
Sam Saccone | d686365 | 2022-11-15 23:57:22 +0000 | [diff] [blame] | 46 | |
Gavin Mak | ea2e330 | 2023-03-11 06:46:20 +0000 | [diff] [blame] | 47 | self.assertIsNone(r.get("GIT_ALTERNATE_OBJECT_DIRECTORIES")) |
| 48 | self.assertEqual( |
| 49 | r.get("GIT_OBJECT_DIRECTORY"), os.path.join("zap", "objects") |
| 50 | ) |
Sam Saccone | d686365 | 2022-11-15 23:57:22 +0000 | [diff] [blame] | 51 | |
Gavin Mak | ea2e330 | 2023-03-11 06:46:20 +0000 | [diff] [blame] | 52 | def test_alternative_setting_when_different(self): |
| 53 | r = git_command._build_env( |
| 54 | objdir=os.path.join("wow", "objects"), gitdir="zap" |
| 55 | ) |
Sam Saccone | d686365 | 2022-11-15 23:57:22 +0000 | [diff] [blame] | 56 | |
Gavin Mak | ea2e330 | 2023-03-11 06:46:20 +0000 | [diff] [blame] | 57 | self.assertEqual( |
| 58 | r.get("GIT_ALTERNATE_OBJECT_DIRECTORIES"), |
| 59 | os.path.join("zap", "objects"), |
| 60 | ) |
| 61 | self.assertEqual( |
| 62 | r.get("GIT_OBJECT_DIRECTORY"), os.path.join("wow", "objects") |
| 63 | ) |
Sam Saccone | d686365 | 2022-11-15 23:57:22 +0000 | [diff] [blame] | 64 | |
| 65 | |
Jason Chang | a6413f5 | 2023-07-26 13:23:40 -0700 | [diff] [blame] | 66 | class GitCommandWaitTest(unittest.TestCase): |
| 67 | """Tests the GitCommand class .Wait()""" |
| 68 | |
| 69 | def setUp(self): |
Mike Frysinger | d4aee65 | 2023-10-19 05:13:32 -0400 | [diff] [blame] | 70 | class MockPopen: |
Jason Chang | a6413f5 | 2023-07-26 13:23:40 -0700 | [diff] [blame] | 71 | rc = 0 |
| 72 | |
Aravind Vasudevan | 2844a5f | 2023-10-06 00:40:25 +0000 | [diff] [blame] | 73 | def __init__(self): |
| 74 | self.stdout = io.BufferedReader(io.BytesIO()) |
| 75 | self.stderr = io.BufferedReader(io.BytesIO()) |
| 76 | |
Jason Chang | a6413f5 | 2023-07-26 13:23:40 -0700 | [diff] [blame] | 77 | def communicate( |
| 78 | self, input: str = None, timeout: float = None |
| 79 | ) -> [str, str]: |
| 80 | """Mock communicate fn.""" |
| 81 | return ["", ""] |
| 82 | |
| 83 | def wait(self, timeout=None): |
| 84 | return self.rc |
| 85 | |
| 86 | self.popen = popen = MockPopen() |
| 87 | |
| 88 | def popen_mock(*args, **kwargs): |
| 89 | return popen |
| 90 | |
| 91 | def realpath_mock(val): |
| 92 | return val |
| 93 | |
| 94 | mock.patch.object(subprocess, "Popen", side_effect=popen_mock).start() |
| 95 | |
| 96 | mock.patch.object( |
| 97 | os.path, "realpath", side_effect=realpath_mock |
| 98 | ).start() |
| 99 | |
| 100 | def tearDown(self): |
| 101 | mock.patch.stopall() |
| 102 | |
| 103 | def test_raises_when_verify_non_zero_result(self): |
| 104 | self.popen.rc = 1 |
| 105 | r = git_command.GitCommand(None, ["status"], verify_command=True) |
| 106 | with self.assertRaises(git_command.GitCommandError): |
| 107 | r.Wait() |
| 108 | |
| 109 | def test_returns_when_no_verify_non_zero_result(self): |
| 110 | self.popen.rc = 1 |
| 111 | r = git_command.GitCommand(None, ["status"], verify_command=False) |
| 112 | self.assertEqual(1, r.Wait()) |
| 113 | |
| 114 | def test_default_returns_non_zero_result(self): |
| 115 | self.popen.rc = 1 |
| 116 | r = git_command.GitCommand(None, ["status"]) |
| 117 | self.assertEqual(1, r.Wait()) |
| 118 | |
| 119 | |
Aravind Vasudevan | 2844a5f | 2023-10-06 00:40:25 +0000 | [diff] [blame] | 120 | class GitCommandStreamLogsTest(unittest.TestCase): |
| 121 | """Tests the GitCommand class stderr log streaming cases.""" |
| 122 | |
| 123 | def setUp(self): |
| 124 | self.mock_process = mock.MagicMock() |
| 125 | self.mock_process.communicate.return_value = (None, None) |
| 126 | self.mock_process.wait.return_value = 0 |
| 127 | |
| 128 | self.mock_popen = mock.MagicMock() |
| 129 | self.mock_popen.return_value = self.mock_process |
| 130 | mock.patch("subprocess.Popen", self.mock_popen).start() |
| 131 | |
| 132 | def tearDown(self): |
| 133 | mock.patch.stopall() |
| 134 | |
| 135 | def test_does_not_stream_logs_when_input_is_set(self): |
| 136 | git_command.GitCommand(None, ["status"], input="foo") |
| 137 | |
| 138 | self.mock_popen.assert_called_once_with( |
| 139 | ["git", "status"], |
| 140 | cwd=None, |
| 141 | env=mock.ANY, |
| 142 | encoding="utf-8", |
| 143 | errors="backslashreplace", |
| 144 | stdin=subprocess.PIPE, |
| 145 | stdout=None, |
| 146 | stderr=None, |
| 147 | ) |
| 148 | self.mock_process.communicate.assert_called_once_with(input="foo") |
| 149 | self.mock_process.stderr.read1.assert_not_called() |
| 150 | |
| 151 | def test_does_not_stream_logs_when_stdout_is_set(self): |
| 152 | git_command.GitCommand(None, ["status"], capture_stdout=True) |
| 153 | |
| 154 | self.mock_popen.assert_called_once_with( |
| 155 | ["git", "status"], |
| 156 | cwd=None, |
| 157 | env=mock.ANY, |
| 158 | encoding="utf-8", |
| 159 | errors="backslashreplace", |
| 160 | stdin=None, |
| 161 | stdout=subprocess.PIPE, |
| 162 | stderr=None, |
| 163 | ) |
| 164 | self.mock_process.communicate.assert_called_once_with(input=None) |
| 165 | self.mock_process.stderr.read1.assert_not_called() |
| 166 | |
| 167 | def test_does_not_stream_logs_when_stderr_is_set(self): |
| 168 | git_command.GitCommand(None, ["status"], capture_stderr=True) |
| 169 | |
| 170 | self.mock_popen.assert_called_once_with( |
| 171 | ["git", "status"], |
| 172 | cwd=None, |
| 173 | env=mock.ANY, |
| 174 | encoding="utf-8", |
| 175 | errors="backslashreplace", |
| 176 | stdin=None, |
| 177 | stdout=None, |
| 178 | stderr=subprocess.PIPE, |
| 179 | ) |
| 180 | self.mock_process.communicate.assert_called_once_with(input=None) |
| 181 | self.mock_process.stderr.read1.assert_not_called() |
| 182 | |
| 183 | def test_does_not_stream_logs_when_merge_output_is_set(self): |
| 184 | git_command.GitCommand(None, ["status"], merge_output=True) |
| 185 | |
| 186 | self.mock_popen.assert_called_once_with( |
| 187 | ["git", "status"], |
| 188 | cwd=None, |
| 189 | env=mock.ANY, |
| 190 | encoding="utf-8", |
| 191 | errors="backslashreplace", |
| 192 | stdin=None, |
| 193 | stdout=None, |
| 194 | stderr=subprocess.STDOUT, |
| 195 | ) |
| 196 | self.mock_process.communicate.assert_called_once_with(input=None) |
| 197 | self.mock_process.stderr.read1.assert_not_called() |
| 198 | |
| 199 | @mock.patch("sys.stderr") |
| 200 | def test_streams_stderr_when_no_stream_is_set(self, mock_stderr): |
| 201 | logs = "\n".join( |
| 202 | [ |
| 203 | "Enumerating objects: 5, done.", |
| 204 | "Counting objects: 100% (5/5), done.", |
| 205 | "Writing objects: 100% (3/3), 330 bytes | 330 KiB/s, done.", |
| 206 | "remote: Processing changes: refs: 1, new: 1, done ", |
| 207 | "remote: SUCCESS", |
| 208 | ] |
| 209 | ) |
| 210 | self.mock_process.stderr = io.BufferedReader( |
| 211 | io.BytesIO(bytes(logs, "utf-8")) |
| 212 | ) |
| 213 | |
| 214 | cmd = git_command.GitCommand(None, ["push"]) |
| 215 | |
| 216 | self.mock_popen.assert_called_once_with( |
| 217 | ["git", "push"], |
| 218 | cwd=None, |
| 219 | env=mock.ANY, |
| 220 | stdin=None, |
| 221 | stdout=None, |
| 222 | stderr=subprocess.PIPE, |
| 223 | ) |
| 224 | self.mock_process.communicate.assert_not_called() |
| 225 | mock_stderr.write.assert_called_once_with(logs) |
| 226 | self.assertEqual(cmd.stderr, logs) |
| 227 | |
| 228 | |
Mike Frysinger | ca540ae | 2019-07-10 15:42:30 -0400 | [diff] [blame] | 229 | class GitCallUnitTest(unittest.TestCase): |
Gavin Mak | ea2e330 | 2023-03-11 06:46:20 +0000 | [diff] [blame] | 230 | """Tests the _GitCall class (via git_command.git).""" |
Mike Frysinger | ca540ae | 2019-07-10 15:42:30 -0400 | [diff] [blame] | 231 | |
Gavin Mak | ea2e330 | 2023-03-11 06:46:20 +0000 | [diff] [blame] | 232 | def test_version_tuple(self): |
| 233 | """Check git.version_tuple() handling.""" |
| 234 | ver = git_command.git.version_tuple() |
| 235 | self.assertIsNotNone(ver) |
Mike Frysinger | ca540ae | 2019-07-10 15:42:30 -0400 | [diff] [blame] | 236 | |
Gavin Mak | ea2e330 | 2023-03-11 06:46:20 +0000 | [diff] [blame] | 237 | # We don't dive too deep into the values here to avoid having to update |
| 238 | # whenever git versions change. We do check relative to this min |
| 239 | # version as this is what `repo` itself requires via MIN_GIT_VERSION. |
| 240 | MIN_GIT_VERSION = (2, 10, 2) |
| 241 | self.assertTrue(isinstance(ver.major, int)) |
| 242 | self.assertTrue(isinstance(ver.minor, int)) |
| 243 | self.assertTrue(isinstance(ver.micro, int)) |
Mike Frysinger | ca540ae | 2019-07-10 15:42:30 -0400 | [diff] [blame] | 244 | |
Gavin Mak | ea2e330 | 2023-03-11 06:46:20 +0000 | [diff] [blame] | 245 | self.assertGreater(ver.major, MIN_GIT_VERSION[0] - 1) |
| 246 | self.assertGreaterEqual(ver.micro, 0) |
| 247 | self.assertGreaterEqual(ver.major, 0) |
Mike Frysinger | ca540ae | 2019-07-10 15:42:30 -0400 | [diff] [blame] | 248 | |
Gavin Mak | ea2e330 | 2023-03-11 06:46:20 +0000 | [diff] [blame] | 249 | self.assertGreaterEqual(ver, MIN_GIT_VERSION) |
| 250 | self.assertLess(ver, (9999, 9999, 9999)) |
Mike Frysinger | ca540ae | 2019-07-10 15:42:30 -0400 | [diff] [blame] | 251 | |
Gavin Mak | ea2e330 | 2023-03-11 06:46:20 +0000 | [diff] [blame] | 252 | self.assertNotEqual("", ver.full) |
Mike Frysinger | 369814b | 2019-07-10 17:10:07 -0400 | [diff] [blame] | 253 | |
| 254 | |
Mike Frysinger | 71b0f31 | 2019-09-30 22:39:49 -0400 | [diff] [blame] | 255 | class UserAgentUnitTest(unittest.TestCase): |
Gavin Mak | ea2e330 | 2023-03-11 06:46:20 +0000 | [diff] [blame] | 256 | """Tests the UserAgent function.""" |
Mike Frysinger | 369814b | 2019-07-10 17:10:07 -0400 | [diff] [blame] | 257 | |
Gavin Mak | ea2e330 | 2023-03-11 06:46:20 +0000 | [diff] [blame] | 258 | def test_smoke_os(self): |
| 259 | """Make sure UA OS setting returns something useful.""" |
| 260 | os_name = git_command.user_agent.os |
| 261 | # We can't dive too deep because of OS/tool differences, but we can |
| 262 | # check the general form. |
| 263 | m = re.match(r"^[^ ]+$", os_name) |
| 264 | self.assertIsNotNone(m) |
Mike Frysinger | 71b0f31 | 2019-09-30 22:39:49 -0400 | [diff] [blame] | 265 | |
Gavin Mak | ea2e330 | 2023-03-11 06:46:20 +0000 | [diff] [blame] | 266 | def test_smoke_repo(self): |
| 267 | """Make sure repo UA returns something useful.""" |
| 268 | ua = git_command.user_agent.repo |
| 269 | # We can't dive too deep because of OS/tool differences, but we can |
| 270 | # check the general form. |
| 271 | m = re.match(r"^git-repo/[^ ]+ ([^ ]+) git/[^ ]+ Python/[0-9.]+", ua) |
| 272 | self.assertIsNotNone(m) |
Mike Frysinger | 2f0951b | 2019-07-10 17:13:46 -0400 | [diff] [blame] | 273 | |
Gavin Mak | ea2e330 | 2023-03-11 06:46:20 +0000 | [diff] [blame] | 274 | def test_smoke_git(self): |
| 275 | """Make sure git UA returns something useful.""" |
| 276 | ua = git_command.user_agent.git |
| 277 | # We can't dive too deep because of OS/tool differences, but we can |
| 278 | # check the general form. |
| 279 | m = re.match(r"^git/[^ ]+ ([^ ]+) git-repo/[^ ]+", ua) |
| 280 | self.assertIsNotNone(m) |
Mike Frysinger | a1e24b1 | 2020-02-19 22:21:21 -0500 | [diff] [blame] | 281 | |
| 282 | |
| 283 | class GitRequireTests(unittest.TestCase): |
Gavin Mak | ea2e330 | 2023-03-11 06:46:20 +0000 | [diff] [blame] | 284 | """Test the git_require helper.""" |
Mike Frysinger | a1e24b1 | 2020-02-19 22:21:21 -0500 | [diff] [blame] | 285 | |
Gavin Mak | ea2e330 | 2023-03-11 06:46:20 +0000 | [diff] [blame] | 286 | def setUp(self): |
| 287 | self.wrapper = wrapper.Wrapper() |
| 288 | ver = self.wrapper.GitVersion(1, 2, 3, 4) |
| 289 | mock.patch.object( |
| 290 | git_command.git, "version_tuple", return_value=ver |
| 291 | ).start() |
Mike Frysinger | a1e24b1 | 2020-02-19 22:21:21 -0500 | [diff] [blame] | 292 | |
Gavin Mak | ea2e330 | 2023-03-11 06:46:20 +0000 | [diff] [blame] | 293 | def tearDown(self): |
| 294 | mock.patch.stopall() |
Mike Frysinger | a1e24b1 | 2020-02-19 22:21:21 -0500 | [diff] [blame] | 295 | |
Gavin Mak | ea2e330 | 2023-03-11 06:46:20 +0000 | [diff] [blame] | 296 | def test_older_nonfatal(self): |
| 297 | """Test non-fatal require calls with old versions.""" |
| 298 | self.assertFalse(git_command.git_require((2,))) |
| 299 | self.assertFalse(git_command.git_require((1, 3))) |
| 300 | self.assertFalse(git_command.git_require((1, 2, 4))) |
| 301 | self.assertFalse(git_command.git_require((1, 2, 3, 5))) |
Mike Frysinger | a1e24b1 | 2020-02-19 22:21:21 -0500 | [diff] [blame] | 302 | |
Gavin Mak | ea2e330 | 2023-03-11 06:46:20 +0000 | [diff] [blame] | 303 | def test_newer_nonfatal(self): |
| 304 | """Test non-fatal require calls with newer versions.""" |
| 305 | self.assertTrue(git_command.git_require((0,))) |
| 306 | self.assertTrue(git_command.git_require((1, 0))) |
| 307 | self.assertTrue(git_command.git_require((1, 2, 0))) |
| 308 | self.assertTrue(git_command.git_require((1, 2, 3, 0))) |
Mike Frysinger | a1e24b1 | 2020-02-19 22:21:21 -0500 | [diff] [blame] | 309 | |
Gavin Mak | ea2e330 | 2023-03-11 06:46:20 +0000 | [diff] [blame] | 310 | def test_equal_nonfatal(self): |
| 311 | """Test require calls with equal values.""" |
| 312 | self.assertTrue(git_command.git_require((1, 2, 3, 4), fail=False)) |
| 313 | self.assertTrue(git_command.git_require((1, 2, 3, 4), fail=True)) |
Mike Frysinger | a1e24b1 | 2020-02-19 22:21:21 -0500 | [diff] [blame] | 314 | |
Gavin Mak | ea2e330 | 2023-03-11 06:46:20 +0000 | [diff] [blame] | 315 | def test_older_fatal(self): |
| 316 | """Test fatal require calls with old versions.""" |
Jason Chang | f9aacd4 | 2023-08-03 14:38:00 -0700 | [diff] [blame] | 317 | with self.assertRaises(git_command.GitRequireError) as e: |
Gavin Mak | ea2e330 | 2023-03-11 06:46:20 +0000 | [diff] [blame] | 318 | git_command.git_require((2,), fail=True) |
| 319 | self.assertNotEqual(0, e.code) |
Mike Frysinger | a1e24b1 | 2020-02-19 22:21:21 -0500 | [diff] [blame] | 320 | |
Gavin Mak | ea2e330 | 2023-03-11 06:46:20 +0000 | [diff] [blame] | 321 | def test_older_fatal_msg(self): |
| 322 | """Test fatal require calls with old versions and message.""" |
Jason Chang | f9aacd4 | 2023-08-03 14:38:00 -0700 | [diff] [blame] | 323 | with self.assertRaises(git_command.GitRequireError) as e: |
Gavin Mak | ea2e330 | 2023-03-11 06:46:20 +0000 | [diff] [blame] | 324 | git_command.git_require((2,), fail=True, msg="so sad") |
| 325 | self.assertNotEqual(0, e.code) |
Aravind Vasudevan | 2844a5f | 2023-10-06 00:40:25 +0000 | [diff] [blame] | 326 | |
| 327 | |
| 328 | class GitCommandErrorTest(unittest.TestCase): |
| 329 | """Test for the GitCommandError class.""" |
| 330 | |
| 331 | def test_augument_stderr(self): |
| 332 | self.assertEqual( |
| 333 | git_command.GitCommandError( |
| 334 | git_stderr="couldn't find remote ref refs/heads/foo" |
| 335 | ).suggestion, |
| 336 | "Check if the provided ref exists in the remote.", |
| 337 | ) |
| 338 | |
| 339 | self.assertEqual( |
| 340 | git_command.GitCommandError( |
| 341 | git_stderr="'foobar' does not appear to be a git repository" |
| 342 | ).suggestion, |
| 343 | "Are you running this repo command outside of a repo workspace?", |
| 344 | ) |