diff options
| author | Aravind Vasudevan <aravindvasudev@google.com> | 2023-10-06 00:40:25 +0000 |
|---|---|---|
| committer | LUCI <gerrit-scoped@luci-project-accounts.iam.gserviceaccount.com> | 2023-10-06 18:21:45 +0000 |
| commit | 2844a5f3cc81ffe2b749e574bdeb61809deab5b9 (patch) | |
| tree | f57cc7f5e9420b47ed4c8506e4d2c3097ac49af5 /tests | |
| parent | 47944bbe2ea69009c0da78573f6536ad2c77f026 (diff) | |
| download | git-repo-2844a5f3cc81ffe2b749e574bdeb61809deab5b9.tar.gz | |
git_command: Augment underlying git errors with suggestions
This change appends suggestions to the underlying git error to make the
error slightly more actionable.
DD: go/improve-repo-error-reporting & go/tee-repo-stderr
Bug: b/292704435
Change-Id: I2bf8bea5fca42c6a9acd2fadc70f58f22456e027
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/387774
Commit-Queue: Aravind Vasudevan <aravindvasudev@google.com>
Reviewed-by: Jason Chang <jasonnc@google.com>
Tested-by: Aravind Vasudevan <aravindvasudev@google.com>
Reviewed-by: Aravind Vasudevan <aravindvasudev@google.com>
Diffstat (limited to 'tests')
| -rw-r--r-- | tests/test_git_command.py | 133 |
1 files changed, 133 insertions, 0 deletions
diff --git a/tests/test_git_command.py b/tests/test_git_command.py index c803d280..881cccb8 100644 --- a/tests/test_git_command.py +++ b/tests/test_git_command.py | |||
| @@ -14,6 +14,7 @@ | |||
| 14 | 14 | ||
| 15 | """Unittests for the git_command.py module.""" | 15 | """Unittests for the git_command.py module.""" |
| 16 | 16 | ||
| 17 | import io | ||
| 17 | import os | 18 | import os |
| 18 | import re | 19 | import re |
| 19 | import subprocess | 20 | import subprocess |
| @@ -74,6 +75,10 @@ class GitCommandWaitTest(unittest.TestCase): | |||
| 74 | class MockPopen(object): | 75 | class MockPopen(object): |
| 75 | rc = 0 | 76 | rc = 0 |
| 76 | 77 | ||
| 78 | def __init__(self): | ||
| 79 | self.stdout = io.BufferedReader(io.BytesIO()) | ||
| 80 | self.stderr = io.BufferedReader(io.BytesIO()) | ||
| 81 | |||
| 77 | def communicate( | 82 | def communicate( |
| 78 | self, input: str = None, timeout: float = None | 83 | self, input: str = None, timeout: float = None |
| 79 | ) -> [str, str]: | 84 | ) -> [str, str]: |
| @@ -117,6 +122,115 @@ class GitCommandWaitTest(unittest.TestCase): | |||
| 117 | self.assertEqual(1, r.Wait()) | 122 | self.assertEqual(1, r.Wait()) |
| 118 | 123 | ||
| 119 | 124 | ||
| 125 | class GitCommandStreamLogsTest(unittest.TestCase): | ||
| 126 | """Tests the GitCommand class stderr log streaming cases.""" | ||
| 127 | |||
| 128 | def setUp(self): | ||
| 129 | self.mock_process = mock.MagicMock() | ||
| 130 | self.mock_process.communicate.return_value = (None, None) | ||
| 131 | self.mock_process.wait.return_value = 0 | ||
| 132 | |||
| 133 | self.mock_popen = mock.MagicMock() | ||
| 134 | self.mock_popen.return_value = self.mock_process | ||
| 135 | mock.patch("subprocess.Popen", self.mock_popen).start() | ||
| 136 | |||
| 137 | def tearDown(self): | ||
| 138 | mock.patch.stopall() | ||
| 139 | |||
| 140 | def test_does_not_stream_logs_when_input_is_set(self): | ||
| 141 | git_command.GitCommand(None, ["status"], input="foo") | ||
| 142 | |||
| 143 | self.mock_popen.assert_called_once_with( | ||
| 144 | ["git", "status"], | ||
| 145 | cwd=None, | ||
| 146 | env=mock.ANY, | ||
| 147 | encoding="utf-8", | ||
| 148 | errors="backslashreplace", | ||
| 149 | stdin=subprocess.PIPE, | ||
| 150 | stdout=None, | ||
| 151 | stderr=None, | ||
| 152 | ) | ||
| 153 | self.mock_process.communicate.assert_called_once_with(input="foo") | ||
| 154 | self.mock_process.stderr.read1.assert_not_called() | ||
| 155 | |||
| 156 | def test_does_not_stream_logs_when_stdout_is_set(self): | ||
| 157 | git_command.GitCommand(None, ["status"], capture_stdout=True) | ||
| 158 | |||
| 159 | self.mock_popen.assert_called_once_with( | ||
| 160 | ["git", "status"], | ||
| 161 | cwd=None, | ||
| 162 | env=mock.ANY, | ||
| 163 | encoding="utf-8", | ||
| 164 | errors="backslashreplace", | ||
| 165 | stdin=None, | ||
| 166 | stdout=subprocess.PIPE, | ||
| 167 | stderr=None, | ||
| 168 | ) | ||
| 169 | self.mock_process.communicate.assert_called_once_with(input=None) | ||
| 170 | self.mock_process.stderr.read1.assert_not_called() | ||
| 171 | |||
| 172 | def test_does_not_stream_logs_when_stderr_is_set(self): | ||
| 173 | git_command.GitCommand(None, ["status"], capture_stderr=True) | ||
| 174 | |||
| 175 | self.mock_popen.assert_called_once_with( | ||
| 176 | ["git", "status"], | ||
| 177 | cwd=None, | ||
| 178 | env=mock.ANY, | ||
| 179 | encoding="utf-8", | ||
| 180 | errors="backslashreplace", | ||
| 181 | stdin=None, | ||
| 182 | stdout=None, | ||
| 183 | stderr=subprocess.PIPE, | ||
| 184 | ) | ||
| 185 | self.mock_process.communicate.assert_called_once_with(input=None) | ||
| 186 | self.mock_process.stderr.read1.assert_not_called() | ||
| 187 | |||
| 188 | def test_does_not_stream_logs_when_merge_output_is_set(self): | ||
| 189 | git_command.GitCommand(None, ["status"], merge_output=True) | ||
| 190 | |||
| 191 | self.mock_popen.assert_called_once_with( | ||
| 192 | ["git", "status"], | ||
| 193 | cwd=None, | ||
| 194 | env=mock.ANY, | ||
| 195 | encoding="utf-8", | ||
| 196 | errors="backslashreplace", | ||
| 197 | stdin=None, | ||
| 198 | stdout=None, | ||
| 199 | stderr=subprocess.STDOUT, | ||
| 200 | ) | ||
| 201 | self.mock_process.communicate.assert_called_once_with(input=None) | ||
| 202 | self.mock_process.stderr.read1.assert_not_called() | ||
| 203 | |||
| 204 | @mock.patch("sys.stderr") | ||
| 205 | def test_streams_stderr_when_no_stream_is_set(self, mock_stderr): | ||
| 206 | logs = "\n".join( | ||
| 207 | [ | ||
| 208 | "Enumerating objects: 5, done.", | ||
| 209 | "Counting objects: 100% (5/5), done.", | ||
| 210 | "Writing objects: 100% (3/3), 330 bytes | 330 KiB/s, done.", | ||
| 211 | "remote: Processing changes: refs: 1, new: 1, done ", | ||
| 212 | "remote: SUCCESS", | ||
| 213 | ] | ||
| 214 | ) | ||
| 215 | self.mock_process.stderr = io.BufferedReader( | ||
| 216 | io.BytesIO(bytes(logs, "utf-8")) | ||
| 217 | ) | ||
| 218 | |||
| 219 | cmd = git_command.GitCommand(None, ["push"]) | ||
| 220 | |||
| 221 | self.mock_popen.assert_called_once_with( | ||
| 222 | ["git", "push"], | ||
| 223 | cwd=None, | ||
| 224 | env=mock.ANY, | ||
| 225 | stdin=None, | ||
| 226 | stdout=None, | ||
| 227 | stderr=subprocess.PIPE, | ||
| 228 | ) | ||
| 229 | self.mock_process.communicate.assert_not_called() | ||
| 230 | mock_stderr.write.assert_called_once_with(logs) | ||
| 231 | self.assertEqual(cmd.stderr, logs) | ||
| 232 | |||
| 233 | |||
| 120 | class GitCallUnitTest(unittest.TestCase): | 234 | class GitCallUnitTest(unittest.TestCase): |
| 121 | """Tests the _GitCall class (via git_command.git).""" | 235 | """Tests the _GitCall class (via git_command.git).""" |
| 122 | 236 | ||
| @@ -214,3 +328,22 @@ class GitRequireTests(unittest.TestCase): | |||
| 214 | with self.assertRaises(git_command.GitRequireError) as e: | 328 | with self.assertRaises(git_command.GitRequireError) as e: |
| 215 | git_command.git_require((2,), fail=True, msg="so sad") | 329 | git_command.git_require((2,), fail=True, msg="so sad") |
| 216 | self.assertNotEqual(0, e.code) | 330 | self.assertNotEqual(0, e.code) |
| 331 | |||
| 332 | |||
| 333 | class GitCommandErrorTest(unittest.TestCase): | ||
| 334 | """Test for the GitCommandError class.""" | ||
| 335 | |||
| 336 | def test_augument_stderr(self): | ||
| 337 | self.assertEqual( | ||
| 338 | git_command.GitCommandError( | ||
| 339 | git_stderr="couldn't find remote ref refs/heads/foo" | ||
| 340 | ).suggestion, | ||
| 341 | "Check if the provided ref exists in the remote.", | ||
| 342 | ) | ||
| 343 | |||
| 344 | self.assertEqual( | ||
| 345 | git_command.GitCommandError( | ||
| 346 | git_stderr="'foobar' does not appear to be a git repository" | ||
| 347 | ).suggestion, | ||
| 348 | "Are you running this repo command outside of a repo workspace?", | ||
| 349 | ) | ||
