summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMike Frysinger <vapier@google.com>2026-03-26 01:54:14 -0400
committerLUCI <gerrit-scoped@luci-project-accounts.iam.gserviceaccount.com>2026-04-06 11:36:39 -0700
commit654690e1b8695d72885533016482306e32528f48 (patch)
treef5c0a5a0be5329d4eb16aa30fd5e58df35d38065
parentac2be4c089fda58b9ba2f7b2bdb1416f8d3a74b2 (diff)
downloadgit-repo-654690e1b8695d72885533016482306e32528f48.tar.gz
tests: convert more tests to pytest
Change-Id: Id4d48b61dc435564c336385bbc4944eb475d1942 Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/569443 Tested-by: Mike Frysinger <vapier@google.com> Commit-Queue: Mike Frysinger <vapier@google.com> Reviewed-by: Gavin Mak <gavinmak@google.com>
-rw-r--r--tests/test_git_trace2_event_log.py755
-rw-r--r--tests/test_manifest_xml.py1036
-rw-r--r--tests/test_subcmds_upload.py81
-rw-r--r--tests/test_wrapper.py642
4 files changed, 1308 insertions, 1206 deletions
diff --git a/tests/test_git_trace2_event_log.py b/tests/test_git_trace2_event_log.py
index be2d09b07..9a6ba2052 100644
--- a/tests/test_git_trace2_event_log.py
+++ b/tests/test_git_trace2_event_log.py
@@ -18,17 +18,24 @@ import contextlib
18import io 18import io
19import json 19import json
20import os 20import os
21import re
21import socket 22import socket
22import tempfile 23import tempfile
23import threading 24import threading
24import unittest 25from typing import Any, Dict, List, Optional
25from unittest import mock 26from unittest import mock
26 27
28import pytest
29
27import git_trace2_event_log 30import git_trace2_event_log
28import platform_utils 31import platform_utils
29 32
30 33
31def serverLoggingThread(socket_path, server_ready, received_traces): 34def server_logging_thread(
35 socket_path: str,
36 server_ready: threading.Condition,
37 received_traces: List[str],
38) -> None:
32 """Helper function to receive logs over a Unix domain socket. 39 """Helper function to receive logs over a Unix domain socket.
33 40
34 Appends received messages on the provided socket and appends to 41 Appends received messages on the provided socket and appends to
@@ -57,405 +64,425 @@ def serverLoggingThread(socket_path, server_ready, received_traces):
57 received_traces.extend(data.decode("utf-8").splitlines()) 64 received_traces.extend(data.decode("utf-8").splitlines())
58 65
59 66
60class EventLogTestCase(unittest.TestCase): 67PARENT_SID_KEY = "GIT_TRACE2_PARENT_SID"
61 """TestCase for the EventLog module.""" 68PARENT_SID_VALUE = "parent_sid"
69SELF_SID_REGEX = r"repo-\d+T\d+Z-.*"
70FULL_SID_REGEX = rf"^{PARENT_SID_VALUE}/{SELF_SID_REGEX}"
71
72
73@pytest.fixture
74def event_log() -> git_trace2_event_log.EventLog:
75 """Fixture for the EventLog module."""
76 # By default we initialize with the expected case where
77 # repo launches us (so GIT_TRACE2_PARENT_SID is set).
78 env = {PARENT_SID_KEY: PARENT_SID_VALUE}
79 return git_trace2_event_log.EventLog(env=env)
80
81
82def verify_common_keys(
83 log_entry: Dict[str, Any],
84 expected_event_name: Optional[str] = None,
85 full_sid: bool = True,
86) -> None:
87 """Helper function to verify common event log keys."""
88 assert "event" in log_entry
89 assert "sid" in log_entry
90 assert "thread" in log_entry
91 assert "time" in log_entry
92
93 # Do basic data format validation.
94 if expected_event_name:
95 assert expected_event_name == log_entry["event"]
96 if full_sid:
97 assert re.match(FULL_SID_REGEX, log_entry["sid"])
98 else:
99 assert re.match(SELF_SID_REGEX, log_entry["sid"])
100 assert re.match(r"^\d+-\d+-\d+T\d+:\d+:\d+\.\d+\+00:00$", log_entry["time"])
101
102
103def read_log(log_path: str) -> List[Dict[str, Any]]:
104 """Helper function to read log data into a list."""
105 log_data = []
106 with open(log_path, mode="rb") as f:
107 for line in f:
108 log_data.append(json.loads(line))
109 return log_data
110
111
112def remove_prefix(s: str, prefix: str) -> str:
113 """Return a copy string after removing |prefix| from |s|, if present or
114 the original string."""
115 if s.startswith(prefix):
116 return s[len(prefix) :]
117 else:
118 return s
119
120
121def test_initial_state_with_parent_sid(
122 event_log: git_trace2_event_log.EventLog,
123) -> None:
124 """Test initial state when 'GIT_TRACE2_PARENT_SID' is set by parent."""
125 assert re.match(FULL_SID_REGEX, event_log.full_sid)
126
127
128def test_initial_state_no_parent_sid() -> None:
129 """Test initial state when 'GIT_TRACE2_PARENT_SID' is not set."""
130 # Setup an empty environment dict (no parent sid).
131 event_log = git_trace2_event_log.EventLog(env={})
132 assert re.match(SELF_SID_REGEX, event_log.full_sid)
133
134
135def test_version_event(event_log: git_trace2_event_log.EventLog) -> None:
136 """Test 'version' event data is valid.
137
138 Verify that the 'version' event is written even when no other
139 events are added.
62 140
63 PARENT_SID_KEY = "GIT_TRACE2_PARENT_SID" 141 Expected event log:
64 PARENT_SID_VALUE = "parent_sid" 142 <version event>
65 SELF_SID_REGEX = r"repo-\d+T\d+Z-.*" 143 """
66 FULL_SID_REGEX = rf"^{PARENT_SID_VALUE}/{SELF_SID_REGEX}" 144 with tempfile.TemporaryDirectory(prefix="event_log_tests") as tempdir:
145 log_path = event_log.Write(path=tempdir)
146 log_data = read_log(log_path)
147
148 # A log with no added events should only have the version entry.
149 assert len(log_data) == 1
150 version_event = log_data[0]
151 verify_common_keys(version_event, expected_event_name="version")
152 # Check for 'version' event specific fields.
153 assert "evt" in version_event
154 assert "exe" in version_event
155 # Verify "evt" version field is a string.
156 assert isinstance(version_event["evt"], str)
157
158
159def test_start_event(event_log: git_trace2_event_log.EventLog) -> None:
160 """Test and validate 'start' event data is valid.
161
162 Expected event log:
163 <version event>
164 <start event>
165 """
166 event_log.StartEvent([])
167 with tempfile.TemporaryDirectory(prefix="event_log_tests") as tempdir:
168 log_path = event_log.Write(path=tempdir)
169 log_data = read_log(log_path)
170
171 assert len(log_data) == 2
172 start_event = log_data[1]
173 verify_common_keys(log_data[0], expected_event_name="version")
174 verify_common_keys(start_event, expected_event_name="start")
175 # Check for 'start' event specific fields.
176 assert "argv" in start_event
177 assert isinstance(start_event["argv"], list)
178
179
180def test_exit_event_result_none(
181 event_log: git_trace2_event_log.EventLog,
182) -> None:
183 """Test 'exit' event data is valid when result is None.
184
185 We expect None result to be converted to 0 in the exit event data.
186
187 Expected event log:
188 <version event>
189 <exit event>
190 """
191 event_log.ExitEvent(None)
192 with tempfile.TemporaryDirectory(prefix="event_log_tests") as tempdir:
193 log_path = event_log.Write(path=tempdir)
194 log_data = read_log(log_path)
195
196 assert len(log_data) == 2
197 exit_event = log_data[1]
198 verify_common_keys(log_data[0], expected_event_name="version")
199 verify_common_keys(exit_event, expected_event_name="exit")
200 # Check for 'exit' event specific fields.
201 assert "code" in exit_event
202 # 'None' result should convert to 0 (successful) return code.
203 assert exit_event["code"] == 0
204
205
206def test_exit_event_result_integer(
207 event_log: git_trace2_event_log.EventLog,
208) -> None:
209 """Test 'exit' event data is valid when result is an integer.
210
211 Expected event log:
212 <version event>
213 <exit event>
214 """
215 event_log.ExitEvent(2)
216 with tempfile.TemporaryDirectory(prefix="event_log_tests") as tempdir:
217 log_path = event_log.Write(path=tempdir)
218 log_data = read_log(log_path)
219
220 assert len(log_data) == 2
221 exit_event = log_data[1]
222 verify_common_keys(log_data[0], expected_event_name="version")
223 verify_common_keys(exit_event, expected_event_name="exit")
224 # Check for 'exit' event specific fields.
225 assert "code" in exit_event
226 assert exit_event["code"] == 2
227
228
229def test_command_event(event_log: git_trace2_event_log.EventLog) -> None:
230 """Test and validate 'command' event data is valid.
231
232 Expected event log:
233 <version event>
234 <command event>
235 """
236 event_log.CommandEvent(name="repo", subcommands=["init", "this"])
237 with tempfile.TemporaryDirectory(prefix="event_log_tests") as tempdir:
238 log_path = event_log.Write(path=tempdir)
239 log_data = read_log(log_path)
240
241 assert len(log_data) == 2
242 command_event = log_data[1]
243 verify_common_keys(log_data[0], expected_event_name="version")
244 verify_common_keys(command_event, expected_event_name="cmd_name")
245 # Check for 'command' event specific fields.
246 assert "name" in command_event
247 assert command_event["name"] == "repo-init-this"
248
249
250def test_def_params_event_repo_config(
251 event_log: git_trace2_event_log.EventLog,
252) -> None:
253 """Test 'def_params' event data outputs only repo config keys.
254
255 Expected event log:
256 <version event>
257 <def_param event>
258 <def_param event>
259 """
260 config = {
261 "git.foo": "bar",
262 "repo.partialclone": "true",
263 "repo.partialclonefilter": "blob:none",
264 }
265 event_log.DefParamRepoEvents(config)
266
267 with tempfile.TemporaryDirectory(prefix="event_log_tests") as tempdir:
268 log_path = event_log.Write(path=tempdir)
269 log_data = read_log(log_path)
270
271 assert len(log_data) == 3
272 def_param_events = log_data[1:]
273 verify_common_keys(log_data[0], expected_event_name="version")
274
275 for event in def_param_events:
276 verify_common_keys(event, expected_event_name="def_param")
277 # Check for 'def_param' event specific fields.
278 assert "param" in event
279 assert "value" in event
280 assert event["param"].startswith("repo.")
281
282
283def test_def_params_event_no_repo_config(
284 event_log: git_trace2_event_log.EventLog,
285) -> None:
286 """Test 'def_params' event data won't output non-repo config keys.
287
288 Expected event log:
289 <version event>
290 """
291 config = {
292 "git.foo": "bar",
293 "git.core.foo2": "baz",
294 }
295 event_log.DefParamRepoEvents(config)
296
297 with tempfile.TemporaryDirectory(prefix="event_log_tests") as tempdir:
298 log_path = event_log.Write(path=tempdir)
299 log_data = read_log(log_path)
300
301 assert len(log_data) == 1
302 verify_common_keys(log_data[0], expected_event_name="version")
67 303
68 def setUp(self):
69 """Load the event_log module every time."""
70 self._event_log = None
71 # By default we initialize with the expected case where
72 # repo launches us (so GIT_TRACE2_PARENT_SID is set).
73 env = {
74 self.PARENT_SID_KEY: self.PARENT_SID_VALUE,
75 }
76 self._event_log = git_trace2_event_log.EventLog(env=env)
77 self._log_data = None
78 304
79 def verifyCommonKeys( 305def test_data_event_config(event_log: git_trace2_event_log.EventLog) -> None:
80 self, log_entry, expected_event_name=None, full_sid=True 306 """Test 'data' event data outputs all config keys.
307
308 Expected event log:
309 <version event>
310 <data event>
311 <data event>
312 """
313 config = {
314 "git.foo": "bar",
315 "repo.partialclone": "false",
316 "repo.syncstate.superproject.hassuperprojecttag": "true",
317 "repo.syncstate.superproject.sys.argv": ["--", "sync", "protobuf"],
318 }
319 prefix_value = "prefix"
320 event_log.LogDataConfigEvents(config, prefix_value)
321
322 with tempfile.TemporaryDirectory(prefix="event_log_tests") as tempdir:
323 log_path = event_log.Write(path=tempdir)
324 log_data = read_log(log_path)
325
326 assert len(log_data) == 5
327 data_events = log_data[1:]
328 verify_common_keys(log_data[0], expected_event_name="version")
329
330 for event in data_events:
331 verify_common_keys(event)
332 # Check for 'data' event specific fields.
333 assert "key" in event
334 assert "value" in event
335 key = event["key"]
336 key = remove_prefix(key, f"{prefix_value}/")
337 value = event["value"]
338 assert event_log.GetDataEventName(value) == event["event"]
339 assert key in config
340 assert value == config[key]
341
342
343def test_error_event(event_log: git_trace2_event_log.EventLog) -> None:
344 """Test and validate 'error' event data is valid.
345
346 Expected event log:
347 <version event>
348 <error event>
349 """
350 msg = "invalid option: --cahced"
351 fmt = "invalid option: %s"
352 event_log.ErrorEvent(msg, fmt)
353 with tempfile.TemporaryDirectory(prefix="event_log_tests") as tempdir:
354 log_path = event_log.Write(path=tempdir)
355 log_data = read_log(log_path)
356
357 assert len(log_data) == 2
358 error_event = log_data[1]
359 verify_common_keys(log_data[0], expected_event_name="version")
360 verify_common_keys(error_event, expected_event_name="error")
361 # Check for 'error' event specific fields.
362 assert "msg" in error_event
363 assert "fmt" in error_event
364 assert error_event["msg"] == f"RepoErrorEvent:{msg}"
365 assert error_event["fmt"] == f"RepoErrorEvent:{fmt}"
366
367
368def test_write_with_filename(event_log: git_trace2_event_log.EventLog) -> None:
369 """Test Write() with a path to a file exits with None."""
370 assert event_log.Write(path="path/to/file") is None
371
372
373def test_write_with_git_config(
374 tmp_path,
375 event_log: git_trace2_event_log.EventLog,
376) -> None:
377 """Test Write() uses the git config path when 'git config' call succeeds."""
378 with mock.patch.object(
379 event_log,
380 "_GetEventTargetPath",
381 return_value=str(tmp_path),
81 ): 382 ):
82 """Helper function to verify common event log keys.""" 383 assert os.path.dirname(event_log.Write()) == str(tmp_path)
83 self.assertIn("event", log_entry) 384
84 self.assertIn("sid", log_entry) 385
85 self.assertIn("thread", log_entry) 386def test_write_no_git_config(event_log: git_trace2_event_log.EventLog) -> None:
86 self.assertIn("time", log_entry) 387 """Test Write() with no git config variable present exits with None."""
87 388 with mock.patch.object(event_log, "_GetEventTargetPath", return_value=None):
88 # Do basic data format validation. 389 assert event_log.Write() is None
89 if expected_event_name: 390
90 self.assertEqual(expected_event_name, log_entry["event"]) 391
91 if full_sid: 392def test_write_non_string(event_log: git_trace2_event_log.EventLog) -> None:
92 self.assertRegex(log_entry["sid"], self.FULL_SID_REGEX) 393 """Test Write() with non-string type for |path| throws TypeError."""
93 else: 394 with pytest.raises(TypeError):
94 self.assertRegex(log_entry["sid"], self.SELF_SID_REGEX) 395 event_log.Write(path=1234)
95 self.assertRegex( 396
96 log_entry["time"], r"^\d+-\d+-\d+T\d+:\d+:\d+\.\d+\+00:00$" 397
398@pytest.mark.skipif(
399 not hasattr(socket, "AF_UNIX"), reason="Requires AF_UNIX sockets"
400)
401def test_write_socket(event_log: git_trace2_event_log.EventLog) -> None:
402 """Test Write() with Unix domain socket and validate received traces."""
403 received_traces: List[str] = []
404 with tempfile.TemporaryDirectory(prefix="test_server_sockets") as tempdir:
405 socket_path = os.path.join(tempdir, "server.sock")
406 server_ready = threading.Condition()
407 # Start "server" listening on Unix domain socket at socket_path.
408 server_thread = threading.Thread(
409 target=server_logging_thread,
410 args=(socket_path, server_ready, received_traces),
97 ) 411 )
412 try:
413 server_thread.start()
98 414
99 def readLog(self, log_path): 415 with server_ready:
100 """Helper function to read log data into a list.""" 416 server_ready.wait(timeout=120)
101 log_data = []
102 with open(log_path, mode="rb") as f:
103 for line in f:
104 log_data.append(json.loads(line))
105 return log_data
106
107 def remove_prefix(self, s, prefix):
108 """Return a copy string after removing |prefix| from |s|, if present or
109 the original string."""
110 if s.startswith(prefix):
111 return s[len(prefix) :]
112 else:
113 return s
114
115 def test_initial_state_with_parent_sid(self):
116 """Test initial state when 'GIT_TRACE2_PARENT_SID' is set by parent."""
117 self.assertRegex(self._event_log.full_sid, self.FULL_SID_REGEX)
118
119 def test_initial_state_no_parent_sid(self):
120 """Test initial state when 'GIT_TRACE2_PARENT_SID' is not set."""
121 # Setup an empty environment dict (no parent sid).
122 self._event_log = git_trace2_event_log.EventLog(env={})
123 self.assertRegex(self._event_log.full_sid, self.SELF_SID_REGEX)
124
125 def test_version_event(self):
126 """Test 'version' event data is valid.
127
128 Verify that the 'version' event is written even when no other
129 events are addded.
130
131 Expected event log:
132 <version event>
133 """
134 with tempfile.TemporaryDirectory(prefix="event_log_tests") as tempdir:
135 log_path = self._event_log.Write(path=tempdir)
136 self._log_data = self.readLog(log_path)
137
138 # A log with no added events should only have the version entry.
139 self.assertEqual(len(self._log_data), 1)
140 version_event = self._log_data[0]
141 self.verifyCommonKeys(version_event, expected_event_name="version")
142 # Check for 'version' event specific fields.
143 self.assertIn("evt", version_event)
144 self.assertIn("exe", version_event)
145 # Verify "evt" version field is a string.
146 self.assertIsInstance(version_event["evt"], str)
147
148 def test_start_event(self):
149 """Test and validate 'start' event data is valid.
150
151 Expected event log:
152 <version event>
153 <start event>
154 """
155 self._event_log.StartEvent([])
156 with tempfile.TemporaryDirectory(prefix="event_log_tests") as tempdir:
157 log_path = self._event_log.Write(path=tempdir)
158 self._log_data = self.readLog(log_path)
159
160 self.assertEqual(len(self._log_data), 2)
161 start_event = self._log_data[1]
162 self.verifyCommonKeys(self._log_data[0], expected_event_name="version")
163 self.verifyCommonKeys(start_event, expected_event_name="start")
164 # Check for 'start' event specific fields.
165 self.assertIn("argv", start_event)
166 self.assertTrue(isinstance(start_event["argv"], list))
167
168 def test_exit_event_result_none(self):
169 """Test 'exit' event data is valid when result is None.
170
171 We expect None result to be converted to 0 in the exit event data.
172
173 Expected event log:
174 <version event>
175 <exit event>
176 """
177 self._event_log.ExitEvent(None)
178 with tempfile.TemporaryDirectory(prefix="event_log_tests") as tempdir:
179 log_path = self._event_log.Write(path=tempdir)
180 self._log_data = self.readLog(log_path)
181
182 self.assertEqual(len(self._log_data), 2)
183 exit_event = self._log_data[1]
184 self.verifyCommonKeys(self._log_data[0], expected_event_name="version")
185 self.verifyCommonKeys(exit_event, expected_event_name="exit")
186 # Check for 'exit' event specific fields.
187 self.assertIn("code", exit_event)
188 # 'None' result should convert to 0 (successful) return code.
189 self.assertEqual(exit_event["code"], 0)
190
191 def test_exit_event_result_integer(self):
192 """Test 'exit' event data is valid when result is an integer.
193
194 Expected event log:
195 <version event>
196 <exit event>
197 """
198 self._event_log.ExitEvent(2)
199 with tempfile.TemporaryDirectory(prefix="event_log_tests") as tempdir:
200 log_path = self._event_log.Write(path=tempdir)
201 self._log_data = self.readLog(log_path)
202
203 self.assertEqual(len(self._log_data), 2)
204 exit_event = self._log_data[1]
205 self.verifyCommonKeys(self._log_data[0], expected_event_name="version")
206 self.verifyCommonKeys(exit_event, expected_event_name="exit")
207 # Check for 'exit' event specific fields.
208 self.assertIn("code", exit_event)
209 self.assertEqual(exit_event["code"], 2)
210
211 def test_command_event(self):
212 """Test and validate 'command' event data is valid.
213
214 Expected event log:
215 <version event>
216 <command event>
217 """
218 self._event_log.CommandEvent(name="repo", subcommands=["init", "this"])
219 with tempfile.TemporaryDirectory(prefix="event_log_tests") as tempdir:
220 log_path = self._event_log.Write(path=tempdir)
221 self._log_data = self.readLog(log_path)
222
223 self.assertEqual(len(self._log_data), 2)
224 command_event = self._log_data[1]
225 self.verifyCommonKeys(self._log_data[0], expected_event_name="version")
226 self.verifyCommonKeys(command_event, expected_event_name="cmd_name")
227 # Check for 'command' event specific fields.
228 self.assertIn("name", command_event)
229 self.assertEqual(command_event["name"], "repo-init-this")
230
231 def test_def_params_event_repo_config(self):
232 """Test 'def_params' event data outputs only repo config keys.
233
234 Expected event log:
235 <version event>
236 <def_param event>
237 <def_param event>
238 """
239 config = {
240 "git.foo": "bar",
241 "repo.partialclone": "true",
242 "repo.partialclonefilter": "blob:none",
243 }
244 self._event_log.DefParamRepoEvents(config)
245
246 with tempfile.TemporaryDirectory(prefix="event_log_tests") as tempdir:
247 log_path = self._event_log.Write(path=tempdir)
248 self._log_data = self.readLog(log_path)
249
250 self.assertEqual(len(self._log_data), 3)
251 def_param_events = self._log_data[1:]
252 self.verifyCommonKeys(self._log_data[0], expected_event_name="version")
253
254 for event in def_param_events:
255 self.verifyCommonKeys(event, expected_event_name="def_param")
256 # Check for 'def_param' event specific fields.
257 self.assertIn("param", event)
258 self.assertIn("value", event)
259 self.assertTrue(event["param"].startswith("repo."))
260
261 def test_def_params_event_no_repo_config(self):
262 """Test 'def_params' event data won't output non-repo config keys.
263
264 Expected event log:
265 <version event>
266 """
267 config = {
268 "git.foo": "bar",
269 "git.core.foo2": "baz",
270 }
271 self._event_log.DefParamRepoEvents(config)
272
273 with tempfile.TemporaryDirectory(prefix="event_log_tests") as tempdir:
274 log_path = self._event_log.Write(path=tempdir)
275 self._log_data = self.readLog(log_path)
276
277 self.assertEqual(len(self._log_data), 1)
278 self.verifyCommonKeys(self._log_data[0], expected_event_name="version")
279
280 def test_data_event_config(self):
281 """Test 'data' event data outputs all config keys.
282
283 Expected event log:
284 <version event>
285 <data event>
286 <data event>
287 """
288 config = {
289 "git.foo": "bar",
290 "repo.partialclone": "false",
291 "repo.syncstate.superproject.hassuperprojecttag": "true",
292 "repo.syncstate.superproject.sys.argv": ["--", "sync", "protobuf"],
293 }
294 prefix_value = "prefix"
295 self._event_log.LogDataConfigEvents(config, prefix_value)
296
297 with tempfile.TemporaryDirectory(prefix="event_log_tests") as tempdir:
298 log_path = self._event_log.Write(path=tempdir)
299 self._log_data = self.readLog(log_path)
300
301 self.assertEqual(len(self._log_data), 5)
302 data_events = self._log_data[1:]
303 self.verifyCommonKeys(self._log_data[0], expected_event_name="version")
304
305 for event in data_events:
306 self.verifyCommonKeys(event)
307 # Check for 'data' event specific fields.
308 self.assertIn("key", event)
309 self.assertIn("value", event)
310 key = event["key"]
311 key = self.remove_prefix(key, f"{prefix_value}/")
312 value = event["value"]
313 self.assertEqual(
314 self._event_log.GetDataEventName(value), event["event"]
315 )
316 self.assertTrue(key in config and value == config[key])
317
318 def test_error_event(self):
319 """Test and validate 'error' event data is valid.
320
321 Expected event log:
322 <version event>
323 <error event>
324 """
325 msg = "invalid option: --cahced"
326 fmt = "invalid option: %s"
327 self._event_log.ErrorEvent(msg, fmt)
328 with tempfile.TemporaryDirectory(prefix="event_log_tests") as tempdir:
329 log_path = self._event_log.Write(path=tempdir)
330 self._log_data = self.readLog(log_path)
331
332 self.assertEqual(len(self._log_data), 2)
333 error_event = self._log_data[1]
334 self.verifyCommonKeys(self._log_data[0], expected_event_name="version")
335 self.verifyCommonKeys(error_event, expected_event_name="error")
336 # Check for 'error' event specific fields.
337 self.assertIn("msg", error_event)
338 self.assertIn("fmt", error_event)
339 self.assertEqual(error_event["msg"], f"RepoErrorEvent:{msg}")
340 self.assertEqual(error_event["fmt"], f"RepoErrorEvent:{fmt}")
341
342 def test_write_with_filename(self):
343 """Test Write() with a path to a file exits with None."""
344 self.assertIsNone(self._event_log.Write(path="path/to/file"))
345
346 def test_write_with_git_config(self):
347 """Test Write() uses the git config path when 'git config' call
348 succeeds."""
349 with tempfile.TemporaryDirectory(prefix="event_log_tests") as tempdir:
350 with mock.patch.object(
351 self._event_log,
352 "_GetEventTargetPath",
353 return_value=tempdir,
354 ):
355 self.assertEqual(
356 os.path.dirname(self._event_log.Write()), tempdir
357 )
358
359 def test_write_no_git_config(self):
360 """Test Write() with no git config variable present exits with None."""
361 with mock.patch.object(
362 self._event_log, "_GetEventTargetPath", return_value=None
363 ):
364 self.assertIsNone(self._event_log.Write())
365
366 def test_write_non_string(self):
367 """Test Write() with non-string type for |path| throws TypeError."""
368 with self.assertRaises(TypeError):
369 self._event_log.Write(path=1234)
370
371 @unittest.skipIf(not hasattr(socket, "AF_UNIX"), "Requires AF_UNIX sockets")
372 def test_write_socket(self):
373 """Test Write() with Unix domain socket for |path| and validate received
374 traces."""
375 received_traces = []
376 with tempfile.TemporaryDirectory(
377 prefix="test_server_sockets"
378 ) as tempdir:
379 socket_path = os.path.join(tempdir, "server.sock")
380 server_ready = threading.Condition()
381 # Start "server" listening on Unix domain socket at socket_path.
382 server_thread = threading.Thread(
383 target=serverLoggingThread,
384 args=(socket_path, server_ready, received_traces),
385 )
386 try:
387 server_thread.start()
388
389 with server_ready:
390 server_ready.wait(timeout=120)
391 417
392 self._event_log.StartEvent([]) 418 event_log.StartEvent([])
393 path = self._event_log.Write(path=f"af_unix:{socket_path}") 419 path = event_log.Write(path=f"af_unix:{socket_path}")
394 finally: 420 finally:
395 server_thread.join(timeout=5) 421 server_thread.join(timeout=5)
396 422
397 self.assertEqual(path, f"af_unix:stream:{socket_path}") 423 assert path == f"af_unix:stream:{socket_path}"
398 self.assertEqual(len(received_traces), 2) 424 assert len(received_traces) == 2
399 version_event = json.loads(received_traces[0]) 425 version_event = json.loads(received_traces[0])
400 start_event = json.loads(received_traces[1]) 426 start_event = json.loads(received_traces[1])
401 self.verifyCommonKeys(version_event, expected_event_name="version") 427 verify_common_keys(version_event, expected_event_name="version")
402 self.verifyCommonKeys(start_event, expected_event_name="start") 428 verify_common_keys(start_event, expected_event_name="start")
403 # Check for 'start' event specific fields. 429 # Check for 'start' event specific fields.
404 self.assertIn("argv", start_event) 430 assert "argv" in start_event
405 self.assertIsInstance(start_event["argv"], list) 431 assert isinstance(start_event["argv"], list)
406 432
407 433
408class EventLogVerboseTestCase(unittest.TestCase): 434class TestEventLogVerbose:
409 """TestCase for the EventLog module verbose logging.""" 435 """TestCase for the EventLog module verbose logging."""
410 436
411 def setUp(self): 437 def test_write_socket_error_no_verbose(self) -> None:
412 self._event_log = git_trace2_event_log.EventLog(env={})
413
414 def test_write_socket_error_no_verbose(self):
415 """Test Write() suppression of socket errors when not verbose.""" 438 """Test Write() suppression of socket errors when not verbose."""
416 self._event_log.verbose = False 439 event_log = git_trace2_event_log.EventLog(env={})
440 event_log.verbose = False
417 with contextlib.redirect_stderr( 441 with contextlib.redirect_stderr(
418 io.StringIO() 442 io.StringIO()
419 ) as mock_stderr, mock.patch("socket.socket", side_effect=OSError): 443 ) as mock_stderr, mock.patch("socket.socket", side_effect=OSError):
420 self._event_log.Write(path="af_unix:stream:/tmp/test_sock") 444 event_log.Write(path="af_unix:stream:/tmp/test_sock")
421 self.assertEqual(mock_stderr.getvalue(), "") 445 assert mock_stderr.getvalue() == ""
422 446
423 def test_write_socket_error_verbose(self): 447 def test_write_socket_error_verbose(self) -> None:
424 """Test Write() printing of socket errors when verbose.""" 448 """Test Write() printing of socket errors when verbose."""
425 self._event_log.verbose = True 449 event_log = git_trace2_event_log.EventLog(env={})
450 event_log.verbose = True
426 with contextlib.redirect_stderr( 451 with contextlib.redirect_stderr(
427 io.StringIO() 452 io.StringIO()
428 ) as mock_stderr, mock.patch( 453 ) as mock_stderr, mock.patch(
429 "socket.socket", side_effect=OSError("Mock error") 454 "socket.socket", side_effect=OSError("Mock error")
430 ): 455 ):
431 self._event_log.Write(path="af_unix:stream:/tmp/test_sock") 456 event_log.Write(path="af_unix:stream:/tmp/test_sock")
432 self.assertIn( 457 assert (
433 "git trace2 logging failed: Mock error", 458 "git trace2 logging failed: Mock error"
434 mock_stderr.getvalue(), 459 in mock_stderr.getvalue()
435 ) 460 )
436 461
437 def test_write_file_error_no_verbose(self): 462 def test_write_file_error_no_verbose(self) -> None:
438 """Test Write() suppression of file errors when not verbose.""" 463 """Test Write() suppression of file errors when not verbose."""
439 self._event_log.verbose = False 464 event_log = git_trace2_event_log.EventLog(env={})
465 event_log.verbose = False
440 with contextlib.redirect_stderr( 466 with contextlib.redirect_stderr(
441 io.StringIO() 467 io.StringIO()
442 ) as mock_stderr, mock.patch( 468 ) as mock_stderr, mock.patch(
443 "tempfile.NamedTemporaryFile", side_effect=FileExistsError 469 "tempfile.NamedTemporaryFile", side_effect=FileExistsError
444 ): 470 ):
445 self._event_log.Write(path="/tmp") 471 event_log.Write(path="/tmp")
446 self.assertEqual(mock_stderr.getvalue(), "") 472 assert mock_stderr.getvalue() == ""
447 473
448 def test_write_file_error_verbose(self): 474 def test_write_file_error_verbose(self) -> None:
449 """Test Write() printing of file errors when verbose.""" 475 """Test Write() printing of file errors when verbose."""
450 self._event_log.verbose = True 476 event_log = git_trace2_event_log.EventLog(env={})
477 event_log.verbose = True
451 with contextlib.redirect_stderr( 478 with contextlib.redirect_stderr(
452 io.StringIO() 479 io.StringIO()
453 ) as mock_stderr, mock.patch( 480 ) as mock_stderr, mock.patch(
454 "tempfile.NamedTemporaryFile", 481 "tempfile.NamedTemporaryFile",
455 side_effect=FileExistsError("Mock error"), 482 side_effect=FileExistsError("Mock error"),
456 ): 483 ):
457 self._event_log.Write(path="/tmp") 484 event_log.Write(path="/tmp")
458 self.assertIn( 485 assert (
459 "git trace2 logging failed: FileExistsError", 486 "git trace2 logging failed: FileExistsError"
460 mock_stderr.getvalue(), 487 in mock_stderr.getvalue()
461 ) 488 )
diff --git a/tests/test_manifest_xml.py b/tests/test_manifest_xml.py
index 5e0c78334..473f781ba 100644
--- a/tests/test_manifest_xml.py
+++ b/tests/test_manifest_xml.py
@@ -18,10 +18,10 @@ import os
18from pathlib import Path 18from pathlib import Path
19import platform 19import platform
20import re 20import re
21import tempfile
22import unittest
23import xml.dom.minidom 21import xml.dom.minidom
24 22
23import pytest
24
25import error 25import error
26import manifest_xml 26import manifest_xml
27 27
@@ -66,7 +66,7 @@ if os.path.sep != "/":
66 ) 66 )
67 67
68 68
69def sort_attributes(manifest): 69def sort_attributes(manifest: str) -> str:
70 """Sort the attributes of all elements alphabetically. 70 """Sort the attributes of all elements alphabetically.
71 71
72 This is needed because different versions of the toxml() function from 72 This is needed because different versions of the toxml() function from
@@ -93,13 +93,12 @@ def sort_attributes(manifest):
93 return new_manifest 93 return new_manifest
94 94
95 95
96class ManifestParseTestCase(unittest.TestCase): 96class RepoClient:
97 """TestCase for parsing manifests.""" 97 """Basic empty repo checkout."""
98 98
99 def setUp(self): 99 def __init__(self, topdir: Path):
100 self.tempdirobj = tempfile.TemporaryDirectory(prefix="repo_tests") 100 self.topdir = topdir
101 self.tempdir = Path(self.tempdirobj.name) 101 self.repodir = self.topdir / ".repo"
102 self.repodir = self.tempdir / ".repo"
103 self.manifest_dir = self.repodir / "manifests" 102 self.manifest_dir = self.repodir / "manifests"
104 self.manifest_file = self.repodir / manifest_xml.MANIFEST_FILE_NAME 103 self.manifest_file = self.repodir / manifest_xml.MANIFEST_FILE_NAME
105 self.local_manifest_dir = ( 104 self.local_manifest_dir = (
@@ -107,7 +106,6 @@ class ManifestParseTestCase(unittest.TestCase):
107 ) 106 )
108 self.repodir.mkdir() 107 self.repodir.mkdir()
109 self.manifest_dir.mkdir() 108 self.manifest_dir.mkdir()
110
111 # The manifest parsing really wants a git repo currently. 109 # The manifest parsing really wants a git repo currently.
112 gitdir = self.repodir / "manifests.git" 110 gitdir = self.repodir / "manifests.git"
113 gitdir.mkdir() 111 gitdir.mkdir()
@@ -117,10 +115,7 @@ class ManifestParseTestCase(unittest.TestCase):
117""" 115"""
118 ) 116 )
119 117
120 def tearDown(self): 118 def get_xml_manifest(self, data: str) -> manifest_xml.XmlManifest:
121 self.tempdirobj.cleanup()
122
123 def getXmlManifest(self, data):
124 """Helper to initialize a manifest for testing.""" 119 """Helper to initialize a manifest for testing."""
125 self.manifest_file.write_text(data, encoding="utf-8") 120 self.manifest_file.write_text(data, encoding="utf-8")
126 return manifest_xml.XmlManifest( 121 return manifest_xml.XmlManifest(
@@ -128,33 +123,43 @@ class ManifestParseTestCase(unittest.TestCase):
128 ) 123 )
129 124
130 @staticmethod 125 @staticmethod
131 def encodeXmlAttr(attr): 126 def encode_xml_attr(attr: str) -> str:
132 """Encode |attr| using XML escape rules.""" 127 """Encode |attr| using XML escape rules."""
133 return attr.replace("\r", "&#x000d;").replace("\n", "&#x000a;") 128 return attr.replace("\r", "&#x000d;").replace("\n", "&#x000a;")
134 129
135 130
136class ManifestValidateFilePaths(unittest.TestCase): 131@pytest.fixture
132def repo_client(tmp_path: Path) -> RepoClient:
133 """Generate a basic empty repo checkout.
134
135 The manifest is not generated.
136 """
137 return RepoClient(tmp_path)
138
139
140class TestManifestValidateFilePaths:
137 """Check _ValidateFilePaths helper. 141 """Check _ValidateFilePaths helper.
138 142
139 This doesn't access a real filesystem. 143 This doesn't access a real filesystem.
140 """ 144 """
141 145
142 def check_both(self, *args): 146 def check_both(self, src: str, dest: str) -> None:
143 manifest_xml.XmlManifest._ValidateFilePaths("copyfile", *args) 147 """Check copyfile & linkfile."""
144 manifest_xml.XmlManifest._ValidateFilePaths("linkfile", *args) 148 manifest_xml.XmlManifest._ValidateFilePaths("copyfile", src, dest)
149 manifest_xml.XmlManifest._ValidateFilePaths("linkfile", src, dest)
145 150
146 def test_normal_path(self): 151 def test_normal_path(self) -> None:
147 """Make sure good paths are accepted.""" 152 """Make sure good paths are accepted."""
148 self.check_both("foo", "bar") 153 self.check_both("foo", "bar")
149 self.check_both("foo/bar", "bar") 154 self.check_both("foo/bar", "bar")
150 self.check_both("foo", "bar/bar") 155 self.check_both("foo", "bar/bar")
151 self.check_both("foo/bar", "bar/bar") 156 self.check_both("foo/bar", "bar/bar")
152 157
153 def test_symlink_targets(self): 158 def test_symlink_targets(self) -> None:
154 """Some extra checks for symlinks.""" 159 """Some extra checks for symlinks."""
155 160
156 def check(*args): 161 def check(src: str, dest: str) -> None:
157 manifest_xml.XmlManifest._ValidateFilePaths("linkfile", *args) 162 manifest_xml.XmlManifest._ValidateFilePaths("linkfile", src, dest)
158 163
159 # We allow symlinks to end in a slash since we allow them to point to 164 # We allow symlinks to end in a slash since we allow them to point to
160 # dirs in general. Technically the slash isn't necessary. 165 # dirs in general. Technically the slash isn't necessary.
@@ -162,114 +167,111 @@ class ManifestValidateFilePaths(unittest.TestCase):
162 # We allow a single '.' to get a reference to the project itself. 167 # We allow a single '.' to get a reference to the project itself.
163 check(".", "bar") 168 check(".", "bar")
164 169
165 def test_bad_paths(self): 170 def test_bad_paths(self) -> None:
166 """Make sure bad paths (src & dest) are rejected.""" 171 """Make sure bad paths (src & dest) are rejected."""
167 for path in INVALID_FS_PATHS: 172 for path in INVALID_FS_PATHS:
168 self.assertRaises( 173 with pytest.raises(error.ManifestInvalidPathError):
169 error.ManifestInvalidPathError, self.check_both, path, "a" 174 self.check_both(path, "a")
170 ) 175 with pytest.raises(error.ManifestInvalidPathError):
171 self.assertRaises( 176 self.check_both("a", path)
172 error.ManifestInvalidPathError, self.check_both, "a", path
173 )
174 177
175 178
176class ValueTests(unittest.TestCase): 179class TestValue:
177 """Check utility parsing code.""" 180 """Check utility parsing code."""
178 181
179 def _get_node(self, text): 182 def _get_node(self, text: str) -> xml.dom.minidom.Element:
180 return xml.dom.minidom.parseString(text).firstChild 183 return xml.dom.minidom.parseString(text).firstChild
181 184
182 def test_bool_default(self): 185 def test_bool_default(self) -> None:
183 """Check XmlBool default handling.""" 186 """Check XmlBool default handling."""
184 node = self._get_node("<node/>") 187 node = self._get_node("<node/>")
185 self.assertIsNone(manifest_xml.XmlBool(node, "a")) 188 assert manifest_xml.XmlBool(node, "a") is None
186 self.assertIsNone(manifest_xml.XmlBool(node, "a", None)) 189 assert manifest_xml.XmlBool(node, "a", None) is None
187 self.assertEqual(123, manifest_xml.XmlBool(node, "a", 123)) 190 assert manifest_xml.XmlBool(node, "a", 123) == 123
188 191
189 node = self._get_node('<node a=""/>') 192 node = self._get_node('<node a=""/>')
190 self.assertIsNone(manifest_xml.XmlBool(node, "a")) 193 assert manifest_xml.XmlBool(node, "a") is None
191 194
192 def test_bool_invalid(self): 195 def test_bool_invalid(self) -> None:
193 """Check XmlBool invalid handling.""" 196 """Check XmlBool invalid handling."""
194 node = self._get_node('<node a="moo"/>') 197 node = self._get_node('<node a="moo"/>')
195 self.assertEqual(123, manifest_xml.XmlBool(node, "a", 123)) 198 assert manifest_xml.XmlBool(node, "a", 123) == 123
196 199
197 def test_bool_true(self): 200 def test_bool_true(self) -> None:
198 """Check XmlBool true values.""" 201 """Check XmlBool true values."""
199 for value in ("yes", "true", "1"): 202 for value in ("yes", "true", "1"):
200 node = self._get_node(f'<node a="{value}"/>') 203 node = self._get_node(f'<node a="{value}"/>')
201 self.assertTrue(manifest_xml.XmlBool(node, "a")) 204 assert manifest_xml.XmlBool(node, "a") is True
202 205
203 def test_bool_false(self): 206 def test_bool_false(self) -> None:
204 """Check XmlBool false values.""" 207 """Check XmlBool false values."""
205 for value in ("no", "false", "0"): 208 for value in ("no", "false", "0"):
206 node = self._get_node(f'<node a="{value}"/>') 209 node = self._get_node(f'<node a="{value}"/>')
207 self.assertFalse(manifest_xml.XmlBool(node, "a")) 210 assert manifest_xml.XmlBool(node, "a") is False
208 211
209 def test_int_default(self): 212 def test_int_default(self) -> None:
210 """Check XmlInt default handling.""" 213 """Check XmlInt default handling."""
211 node = self._get_node("<node/>") 214 node = self._get_node("<node/>")
212 self.assertIsNone(manifest_xml.XmlInt(node, "a")) 215 assert manifest_xml.XmlInt(node, "a") is None
213 self.assertIsNone(manifest_xml.XmlInt(node, "a", None)) 216 assert manifest_xml.XmlInt(node, "a", None) is None
214 self.assertEqual(123, manifest_xml.XmlInt(node, "a", 123)) 217 assert manifest_xml.XmlInt(node, "a", 123) == 123
215 218
216 node = self._get_node('<node a=""/>') 219 node = self._get_node('<node a=""/>')
217 self.assertIsNone(manifest_xml.XmlInt(node, "a")) 220 assert manifest_xml.XmlInt(node, "a") is None
218 221
219 def test_int_good(self): 222 def test_int_good(self) -> None:
220 """Check XmlInt numeric handling.""" 223 """Check XmlInt numeric handling."""
221 for value in (-1, 0, 1, 50000): 224 for value in (-1, 0, 1, 50000):
222 node = self._get_node(f'<node a="{value}"/>') 225 node = self._get_node(f'<node a="{value}"/>')
223 self.assertEqual(value, manifest_xml.XmlInt(node, "a")) 226 assert manifest_xml.XmlInt(node, "a") == value
224 227
225 def test_int_invalid(self): 228 def test_int_invalid(self) -> None:
226 """Check XmlInt invalid handling.""" 229 """Check XmlInt invalid handling."""
227 with self.assertRaises(error.ManifestParseError): 230 with pytest.raises(error.ManifestParseError):
228 node = self._get_node('<node a="xx"/>') 231 node = self._get_node('<node a="xx"/>')
229 manifest_xml.XmlInt(node, "a") 232 manifest_xml.XmlInt(node, "a")
230 233
231 234
232class XmlManifestTests(ManifestParseTestCase): 235class TestXmlManifest:
233 """Check manifest processing.""" 236 """Check manifest processing."""
234 237
235 def test_empty(self): 238 def test_empty(self, repo_client: RepoClient) -> None:
236 """Parse an 'empty' manifest file.""" 239 """Parse an 'empty' manifest file."""
237 manifest = self.getXmlManifest( 240 manifest = repo_client.get_xml_manifest(
238 '<?xml version="1.0" encoding="UTF-8"?>' "<manifest></manifest>" 241 '<?xml version="1.0" encoding="UTF-8"?>' "<manifest></manifest>"
239 ) 242 )
240 self.assertEqual(manifest.remotes, {}) 243 assert manifest.remotes == {}
241 self.assertEqual(manifest.projects, []) 244 assert manifest.projects == []
242 245
243 def test_link(self): 246 def test_link(self, repo_client: RepoClient) -> None:
244 """Verify Link handling with new names.""" 247 """Verify Link handling with new names."""
245 manifest = manifest_xml.XmlManifest( 248 manifest = repo_client.get_xml_manifest("<manifest></manifest>")
246 str(self.repodir), str(self.manifest_file) 249 (repo_client.manifest_dir / "foo.xml").write_text(
250 "<manifest></manifest>"
247 ) 251 )
248 (self.manifest_dir / "foo.xml").write_text("<manifest></manifest>")
249 manifest.Link("foo.xml") 252 manifest.Link("foo.xml")
250 self.assertIn( 253 assert (
251 '<include name="foo.xml" />', self.manifest_file.read_text() 254 '<include name="foo.xml" />'
255 in repo_client.manifest_file.read_text()
252 ) 256 )
253 257
254 def test_toxml_empty(self): 258 def test_toxml_empty(self, repo_client: RepoClient) -> None:
255 """Verify the ToXml() helper.""" 259 """Verify the ToXml() helper."""
256 manifest = self.getXmlManifest( 260 manifest = repo_client.get_xml_manifest(
257 '<?xml version="1.0" encoding="UTF-8"?>' "<manifest></manifest>" 261 '<?xml version="1.0" encoding="UTF-8"?>' "<manifest></manifest>"
258 ) 262 )
259 self.assertEqual( 263 assert manifest.ToXml().toxml() == '<?xml version="1.0" ?><manifest/>'
260 manifest.ToXml().toxml(), '<?xml version="1.0" ?><manifest/>'
261 )
262 264
263 def test_todict_empty(self): 265 def test_todict_empty(self, repo_client: RepoClient) -> None:
264 """Verify the ToDict() helper.""" 266 """Verify the ToDict() helper."""
265 manifest = self.getXmlManifest( 267 manifest = repo_client.get_xml_manifest(
266 '<?xml version="1.0" encoding="UTF-8"?>' "<manifest></manifest>" 268 '<?xml version="1.0" encoding="UTF-8"?>' "<manifest></manifest>"
267 ) 269 )
268 self.assertEqual(manifest.ToDict(), {}) 270 assert manifest.ToDict() == {}
269 271
270 def test_toxml_omit_local(self): 272 def test_toxml_omit_local(self, repo_client: RepoClient) -> None:
271 """Does not include local_manifests projects when omit_local=True.""" 273 """Does not include local_manifests projects when omit_local=True."""
272 manifest = self.getXmlManifest( 274 manifest = repo_client.get_xml_manifest(
273 '<?xml version="1.0" encoding="UTF-8"?><manifest>' 275 '<?xml version="1.0" encoding="UTF-8"?><manifest>'
274 '<remote name="a" fetch=".."/><default remote="a" revision="r"/>' 276 '<remote name="a" fetch=".."/><default remote="a" revision="r"/>'
275 '<project name="p" groups="local::me"/>' 277 '<project name="p" groups="local::me"/>'
@@ -277,16 +279,16 @@ class XmlManifestTests(ManifestParseTestCase):
277 '<project name="r" groups="keep"/>' 279 '<project name="r" groups="keep"/>'
278 "</manifest>" 280 "</manifest>"
279 ) 281 )
280 self.assertEqual( 282 assert (
281 sort_attributes(manifest.ToXml(omit_local=True).toxml()), 283 sort_attributes(manifest.ToXml(omit_local=True).toxml())
282 '<?xml version="1.0" ?><manifest>' 284 == '<?xml version="1.0" ?><manifest>'
283 '<remote fetch=".." name="a"/><default remote="a" revision="r"/>' 285 '<remote fetch=".." name="a"/><default remote="a" revision="r"/>'
284 '<project name="q"/><project groups="keep" name="r"/></manifest>', 286 '<project name="q"/><project groups="keep" name="r"/></manifest>'
285 ) 287 )
286 288
287 def test_toxml_with_local(self): 289 def test_toxml_with_local(self, repo_client: RepoClient) -> None:
288 """Does include local_manifests projects when omit_local=False.""" 290 """Does include local_manifests projects when omit_local=False."""
289 manifest = self.getXmlManifest( 291 manifest = repo_client.get_xml_manifest(
290 '<?xml version="1.0" encoding="UTF-8"?><manifest>' 292 '<?xml version="1.0" encoding="UTF-8"?><manifest>'
291 '<remote name="a" fetch=".."/><default remote="a" revision="r"/>' 293 '<remote name="a" fetch=".."/><default remote="a" revision="r"/>'
292 '<project name="p" groups="local::me"/>' 294 '<project name="p" groups="local::me"/>'
@@ -294,17 +296,17 @@ class XmlManifestTests(ManifestParseTestCase):
294 '<project name="r" groups="keep"/>' 296 '<project name="r" groups="keep"/>'
295 "</manifest>" 297 "</manifest>"
296 ) 298 )
297 self.assertEqual( 299 assert (
298 sort_attributes(manifest.ToXml(omit_local=False).toxml()), 300 sort_attributes(manifest.ToXml(omit_local=False).toxml())
299 '<?xml version="1.0" ?><manifest>' 301 == '<?xml version="1.0" ?><manifest>'
300 '<remote fetch=".." name="a"/><default remote="a" revision="r"/>' 302 '<remote fetch=".." name="a"/><default remote="a" revision="r"/>'
301 '<project groups="local::me" name="p"/>' 303 '<project groups="local::me" name="p"/>'
302 '<project name="q"/><project groups="keep" name="r"/></manifest>', 304 '<project name="q"/><project groups="keep" name="r"/></manifest>'
303 ) 305 )
304 306
305 def test_repo_hooks(self): 307 def test_repo_hooks(self, repo_client: RepoClient) -> None:
306 """Check repo-hooks settings.""" 308 """Check repo-hooks settings."""
307 manifest = self.getXmlManifest( 309 manifest = repo_client.get_xml_manifest(
308 """ 310 """
309<manifest> 311<manifest>
310 <remote name="test-remote" fetch="http://localhost" /> 312 <remote name="test-remote" fetch="http://localhost" />
@@ -314,14 +316,12 @@ class XmlManifestTests(ManifestParseTestCase):
314</manifest> 316</manifest>
315""" 317"""
316 ) 318 )
317 self.assertEqual(manifest.repo_hooks_project.name, "repohooks") 319 assert manifest.repo_hooks_project.name == "repohooks"
318 self.assertEqual( 320 assert manifest.repo_hooks_project.enabled_repo_hooks == ["a", "b"]
319 manifest.repo_hooks_project.enabled_repo_hooks, ["a", "b"]
320 )
321 321
322 def test_repo_hooks_unordered(self): 322 def test_repo_hooks_unordered(self, repo_client: RepoClient) -> None:
323 """Check repo-hooks settings work even if the project def comes second.""" # noqa: E501 323 """Check repo-hooks settings work when the project comes after."""
324 manifest = self.getXmlManifest( 324 manifest = repo_client.get_xml_manifest(
325 """ 325 """
326<manifest> 326<manifest>
327 <remote name="test-remote" fetch="http://localhost" /> 327 <remote name="test-remote" fetch="http://localhost" />
@@ -331,14 +331,12 @@ class XmlManifestTests(ManifestParseTestCase):
331</manifest> 331</manifest>
332""" 332"""
333 ) 333 )
334 self.assertEqual(manifest.repo_hooks_project.name, "repohooks") 334 assert manifest.repo_hooks_project.name == "repohooks"
335 self.assertEqual( 335 assert manifest.repo_hooks_project.enabled_repo_hooks == ["a", "b"]
336 manifest.repo_hooks_project.enabled_repo_hooks, ["a", "b"]
337 )
338 336
339 def test_unknown_tags(self): 337 def test_unknown_tags(self, repo_client: RepoClient) -> None:
340 """Check superproject settings.""" 338 """Check superproject settings."""
341 manifest = self.getXmlManifest( 339 manifest = repo_client.get_xml_manifest(
342 """ 340 """
343<manifest> 341<manifest>
344 <remote name="test-remote" fetch="http://localhost" /> 342 <remote name="test-remote" fetch="http://localhost" />
@@ -349,20 +347,20 @@ class XmlManifestTests(ManifestParseTestCase):
349</manifest> 347</manifest>
350""" 348"""
351 ) 349 )
352 self.assertEqual(manifest.superproject.name, "superproject") 350 assert manifest.superproject.name == "superproject"
353 self.assertEqual(manifest.superproject.remote.name, "test-remote") 351 assert manifest.superproject.remote.name == "test-remote"
354 self.assertEqual( 352 assert (
355 sort_attributes(manifest.ToXml().toxml()), 353 sort_attributes(manifest.ToXml().toxml())
356 '<?xml version="1.0" ?><manifest>' 354 == '<?xml version="1.0" ?><manifest>'
357 '<remote fetch="http://localhost" name="test-remote"/>' 355 '<remote fetch="http://localhost" name="test-remote"/>'
358 '<default remote="test-remote" revision="refs/heads/main"/>' 356 '<default remote="test-remote" revision="refs/heads/main"/>'
359 '<superproject name="superproject"/>' 357 '<superproject name="superproject"/>'
360 "</manifest>", 358 "</manifest>"
361 ) 359 )
362 360
363 def test_remote_annotations(self): 361 def test_remote_annotations(self, repo_client: RepoClient) -> None:
364 """Check remote settings.""" 362 """Check remote settings."""
365 manifest = self.getXmlManifest( 363 manifest = repo_client.get_xml_manifest(
366 """ 364 """
367<manifest> 365<manifest>
368 <remote name="test-remote" fetch="http://localhost"> 366 <remote name="test-remote" fetch="http://localhost">
@@ -371,24 +369,20 @@ class XmlManifestTests(ManifestParseTestCase):
371</manifest> 369</manifest>
372""" 370"""
373 ) 371 )
374 self.assertEqual( 372 assert manifest.remotes["test-remote"].annotations[0].name == "foo"
375 manifest.remotes["test-remote"].annotations[0].name, "foo" 373 assert manifest.remotes["test-remote"].annotations[0].value == "bar"
376 ) 374 assert (
377 self.assertEqual( 375 sort_attributes(manifest.ToXml().toxml())
378 manifest.remotes["test-remote"].annotations[0].value, "bar" 376 == '<?xml version="1.0" ?><manifest>'
379 )
380 self.assertEqual(
381 sort_attributes(manifest.ToXml().toxml()),
382 '<?xml version="1.0" ?><manifest>'
383 '<remote fetch="http://localhost" name="test-remote">' 377 '<remote fetch="http://localhost" name="test-remote">'
384 '<annotation name="foo" value="bar"/>' 378 '<annotation name="foo" value="bar"/>'
385 "</remote>" 379 "</remote>"
386 "</manifest>", 380 "</manifest>"
387 ) 381 )
388 382
389 def test_parse_with_xml_doctype(self): 383 def test_parse_with_xml_doctype(self, repo_client: RepoClient) -> None:
390 """Check correct manifest parse with DOCTYPE node present.""" 384 """Check correct manifest parse with DOCTYPE node present."""
391 manifest = self.getXmlManifest( 385 manifest = repo_client.get_xml_manifest(
392 """<?xml version="1.0" encoding="UTF-8"?> 386 """<?xml version="1.0" encoding="UTF-8"?>
393<!DOCTYPE manifest []> 387<!DOCTYPE manifest []>
394<manifest> 388<manifest>
@@ -398,42 +392,41 @@ class XmlManifestTests(ManifestParseTestCase):
398</manifest> 392</manifest>
399""" 393"""
400 ) 394 )
401 self.assertEqual(len(manifest.projects), 1) 395 assert len(manifest.projects) == 1
402 self.assertEqual(manifest.projects[0].name, "test-project") 396 assert manifest.projects[0].name == "test-project"
403 397
404 def test_sync_j_max(self): 398 def test_sync_j_max(self, repo_client: RepoClient) -> None:
405 """Check sync-j-max handling.""" 399 """Check sync-j-max handling."""
406 # Check valid value. 400 # Check valid value.
407 manifest = self.getXmlManifest( 401 manifest = repo_client.get_xml_manifest(
408 '<manifest><default sync-j-max="5" /></manifest>' 402 '<manifest><default sync-j-max="5" /></manifest>'
409 ) 403 )
410 self.assertEqual(manifest.default.sync_j_max, 5) 404 assert manifest.default.sync_j_max == 5
411 self.assertEqual( 405 assert (
412 manifest.ToXml().toxml(), 406 manifest.ToXml().toxml() == '<?xml version="1.0" ?>'
413 '<?xml version="1.0" ?>' 407 '<manifest><default sync-j-max="5"/></manifest>'
414 '<manifest><default sync-j-max="5"/></manifest>',
415 ) 408 )
416 409
417 # Check invalid values. 410 # Check invalid values.
418 with self.assertRaises(error.ManifestParseError): 411 with pytest.raises(error.ManifestParseError):
419 manifest = self.getXmlManifest( 412 manifest = repo_client.get_xml_manifest(
420 '<manifest><default sync-j-max="0" /></manifest>' 413 '<manifest><default sync-j-max="0" /></manifest>'
421 ) 414 )
422 manifest.ToXml() 415 manifest.ToXml()
423 416
424 with self.assertRaises(error.ManifestParseError): 417 with pytest.raises(error.ManifestParseError):
425 manifest = self.getXmlManifest( 418 manifest = repo_client.get_xml_manifest(
426 '<manifest><default sync-j-max="-1" /></manifest>' 419 '<manifest><default sync-j-max="-1" /></manifest>'
427 ) 420 )
428 manifest.ToXml() 421 manifest.ToXml()
429 422
430 423
431class IncludeElementTests(ManifestParseTestCase): 424class TestIncludeElement:
432 """Tests for <include>.""" 425 """Tests for <include>."""
433 426
434 def test_revision_default(self): 427 def test_revision_default(self, repo_client: RepoClient) -> None:
435 """Check handling of revision attribute.""" 428 """Check handling of revision attribute."""
436 root_m = self.manifest_dir / "root.xml" 429 root_m = repo_client.manifest_dir / "root.xml"
437 root_m.write_text( 430 root_m.write_text(
438 """ 431 """
439<manifest> 432<manifest>
@@ -445,7 +438,7 @@ class IncludeElementTests(ManifestParseTestCase):
445</manifest> 438</manifest>
446""" 439"""
447 ) 440 )
448 (self.manifest_dir / "stable.xml").write_text( 441 (repo_client.manifest_dir / "stable.xml").write_text(
449 """ 442 """
450<manifest> 443<manifest>
451 <include name="man1.xml" /> 444 <include name="man1.xml" />
@@ -455,7 +448,7 @@ class IncludeElementTests(ManifestParseTestCase):
455</manifest> 448</manifest>
456""" 449"""
457 ) 450 )
458 (self.manifest_dir / "man1.xml").write_text( 451 (repo_client.manifest_dir / "man1.xml").write_text(
459 """ 452 """
460<manifest> 453<manifest>
461 <project name="man1-name1" /> 454 <project name="man1-name1" />
@@ -463,7 +456,7 @@ class IncludeElementTests(ManifestParseTestCase):
463</manifest> 456</manifest>
464""" 457"""
465 ) 458 )
466 (self.manifest_dir / "man2.xml").write_text( 459 (repo_client.manifest_dir / "man2.xml").write_text(
467 """ 460 """
468<manifest> 461<manifest>
469 <project name="man2-name1" /> 462 <project name="man2-name1" />
@@ -471,31 +464,34 @@ class IncludeElementTests(ManifestParseTestCase):
471</manifest> 464</manifest>
472""" 465"""
473 ) 466 )
474 include_m = manifest_xml.XmlManifest(str(self.repodir), str(root_m)) 467 include_m = manifest_xml.XmlManifest(
468 str(repo_client.repodir), str(root_m)
469 )
475 for proj in include_m.projects: 470 for proj in include_m.projects:
476 if proj.name == "root-name1": 471 if proj.name == "root-name1":
477 # Check include revision not set on root level proj. 472 # Check include revision not set on root level proj.
478 self.assertNotEqual("stable-branch", proj.revisionExpr) 473 assert proj.revisionExpr != "stable-branch"
479 if proj.name == "root-name2": 474 if proj.name == "root-name2":
480 # Check root proj revision not removed. 475 # Check root proj revision not removed.
481 self.assertEqual("refs/heads/main", proj.revisionExpr) 476 assert proj.revisionExpr == "refs/heads/main"
482 if proj.name == "stable-name1": 477 if proj.name == "stable-name1":
483 # Check stable proj has inherited revision include node. 478 # Check stable proj has inherited revision include node.
484 self.assertEqual("stable-branch", proj.revisionExpr) 479 assert proj.revisionExpr == "stable-branch"
485 if proj.name == "stable-name2": 480 if proj.name == "stable-name2":
486 # Check stable proj revision can override include node. 481 # Check stable proj revision can override include node.
487 self.assertEqual("stable-branch2", proj.revisionExpr) 482 assert proj.revisionExpr == "stable-branch2"
488 if proj.name == "man1-name1": 483 if proj.name == "man1-name1":
489 self.assertEqual("stable-branch", proj.revisionExpr) 484 assert proj.revisionExpr == "stable-branch"
490 if proj.name == "man1-name2": 485 if proj.name == "man1-name2":
491 self.assertEqual("stable-branch3", proj.revisionExpr) 486 assert proj.revisionExpr == "stable-branch3"
492 if proj.name == "man2-name1": 487 if proj.name == "man2-name1":
493 self.assertEqual("stable-branch2", proj.revisionExpr) 488 assert proj.revisionExpr == "stable-branch2"
494 if proj.name == "man2-name2": 489 if proj.name == "man2-name2":
495 self.assertEqual("stable-branch3", proj.revisionExpr) 490 assert proj.revisionExpr == "stable-branch3"
496 491
497 def test_group_levels(self): 492 def test_group_levels(self, repo_client: RepoClient) -> None:
498 root_m = self.manifest_dir / "root.xml" 493 """Check handling of nested include groups."""
494 root_m = repo_client.manifest_dir / "root.xml"
499 root_m.write_text( 495 root_m.write_text(
500 """ 496 """
501<manifest> 497<manifest>
@@ -507,7 +503,7 @@ class IncludeElementTests(ManifestParseTestCase):
507</manifest> 503</manifest>
508""" 504"""
509 ) 505 )
510 (self.manifest_dir / "level1.xml").write_text( 506 (repo_client.manifest_dir / "level1.xml").write_text(
511 """ 507 """
512<manifest> 508<manifest>
513 <include name="level2.xml" groups="level2-group" /> 509 <include name="level2.xml" groups="level2-group" />
@@ -515,33 +511,38 @@ class IncludeElementTests(ManifestParseTestCase):
515</manifest> 511</manifest>
516""" 512"""
517 ) 513 )
518 (self.manifest_dir / "level2.xml").write_text( 514 (repo_client.manifest_dir / "level2.xml").write_text(
519 """ 515 """
520<manifest> 516<manifest>
521 <project name="level2-name1" path="level2-path1" groups="l2g1,l2g2" /> 517 <project name="level2-name1" path="level2-path1" groups="l2g1,l2g2" />
522</manifest> 518</manifest>
523""" 519"""
524 ) 520 )
525 include_m = manifest_xml.XmlManifest(str(self.repodir), str(root_m)) 521 include_m = manifest_xml.XmlManifest(
522 str(repo_client.repodir), str(root_m)
523 )
526 for proj in include_m.projects: 524 for proj in include_m.projects:
527 if proj.name == "root-name1": 525 if proj.name == "root-name1":
528 # Check include group not set on root level proj. 526 # Check include group not set on root level proj.
529 self.assertNotIn("level1-group", proj.groups) 527 assert "level1-group" not in proj.groups
530 if proj.name == "root-name2": 528 if proj.name == "root-name2":
531 # Check root proj group not removed. 529 # Check root proj group not removed.
532 self.assertIn("r2g1", proj.groups) 530 assert "r2g1" in proj.groups
533 if proj.name == "level1-name1": 531 if proj.name == "level1-name1":
534 # Check level1 proj has inherited group level 1. 532 # Check level1 proj has inherited group level 1.
535 self.assertIn("level1-group", proj.groups) 533 assert "level1-group" in proj.groups
536 if proj.name == "level2-name1": 534 if proj.name == "level2-name1":
537 # Check level2 proj has inherited group levels 1 and 2. 535 # Check level2 proj has inherited group levels 1 and 2.
538 self.assertIn("level1-group", proj.groups) 536 assert "level1-group" in proj.groups
539 self.assertIn("level2-group", proj.groups) 537 assert "level2-group" in proj.groups
540 # Check level2 proj group not removed. 538 # Check level2 proj group not removed.
541 self.assertIn("l2g1", proj.groups) 539 assert "l2g1" in proj.groups
542 540
543 def test_group_levels_with_extend_project(self): 541 def test_group_levels_with_extend_project(
544 root_m = self.manifest_dir / "root.xml" 542 self, repo_client: RepoClient
543 ) -> None:
544 """Check inheritance of groups via extend-project."""
545 root_m = repo_client.manifest_dir / "root.xml"
545 root_m.write_text( 546 root_m.write_text(
546 """ 547 """
547<manifest> 548<manifest>
@@ -552,32 +553,36 @@ class IncludeElementTests(ManifestParseTestCase):
552</manifest> 553</manifest>
553""" 554"""
554 ) 555 )
555 (self.manifest_dir / "man1.xml").write_text( 556 (repo_client.manifest_dir / "man1.xml").write_text(
556 """ 557 """
557<manifest> 558<manifest>
558 <project name="project1" path="project1" /> 559 <project name="project1" path="project1" />
559</manifest> 560</manifest>
560""" 561"""
561 ) 562 )
562 (self.manifest_dir / "man2.xml").write_text( 563 (repo_client.manifest_dir / "man2.xml").write_text(
563 """ 564 """
564<manifest> 565<manifest>
565 <extend-project name="project1" groups="eg1" /> 566 <extend-project name="project1" groups="eg1" />
566</manifest> 567</manifest>
567""" 568"""
568 ) 569 )
569 include_m = manifest_xml.XmlManifest(str(self.repodir), str(root_m)) 570 include_m = manifest_xml.XmlManifest(
571 str(repo_client.repodir), str(root_m)
572 )
570 proj = include_m.projects[0] 573 proj = include_m.projects[0]
571 # Check project has inherited group via project element. 574 # Check project has inherited group via project element.
572 self.assertIn("top-group1", proj.groups) 575 assert "top-group1" in proj.groups
573 # Check project has inherited group via extend-project element. 576 # Check project has inherited group via extend-project element.
574 self.assertIn("top-group2", proj.groups) 577 assert "top-group2" in proj.groups
575 # Check project has set group via extend-project element. 578 # Check project has set group via extend-project element.
576 self.assertIn("eg1", proj.groups) 579 assert "eg1" in proj.groups
577 580
578 def test_extend_project_does_not_inherit_local_groups(self): 581 def test_extend_project_does_not_inherit_local_groups(
582 self, repo_client: RepoClient
583 ) -> None:
579 """Check that extend-project does not inherit local groups.""" 584 """Check that extend-project does not inherit local groups."""
580 root_m = self.manifest_dir / "root.xml" 585 root_m = repo_client.manifest_dir / "root.xml"
581 root_m.write_text( 586 root_m.write_text(
582 """ 587 """
583<manifest> 588<manifest>
@@ -588,26 +593,28 @@ class IncludeElementTests(ManifestParseTestCase):
588</manifest> 593</manifest>
589""" 594"""
590 ) 595 )
591 (self.manifest_dir / "man1.xml").write_text( 596 (repo_client.manifest_dir / "man1.xml").write_text(
592 """ 597 """
593<manifest> 598<manifest>
594 <extend-project name="project1" groups="g3" /> 599 <extend-project name="project1" groups="g3" />
595</manifest> 600</manifest>
596""" 601"""
597 ) 602 )
598 include_m = manifest_xml.XmlManifest(str(self.repodir), str(root_m)) 603 include_m = manifest_xml.XmlManifest(
604 str(repo_client.repodir), str(root_m)
605 )
599 proj = include_m.projects[0] 606 proj = include_m.projects[0]
600 607
601 self.assertIn("g1", proj.groups) 608 assert "g1" in proj.groups
602 self.assertNotIn("local:g2", proj.groups) 609 assert "local:g2" not in proj.groups
603 self.assertIn("g3", proj.groups) 610 assert "g3" in proj.groups
604 611
605 def test_allow_bad_name_from_user(self): 612 def test_allow_bad_name_from_user(self, repo_client: RepoClient) -> None:
606 """Check handling of bad name attribute from the user's input.""" 613 """Check handling of bad name attribute from the user's input."""
607 614
608 def parse(name): 615 def parse(name: str) -> None:
609 name = self.encodeXmlAttr(name) 616 name = repo_client.encode_xml_attr(name)
610 manifest = self.getXmlManifest( 617 manifest = repo_client.get_xml_manifest(
611 f""" 618 f"""
612<manifest> 619<manifest>
613 <remote name="default-remote" fetch="http://localhost" /> 620 <remote name="default-remote" fetch="http://localhost" />
@@ -620,26 +627,26 @@ class IncludeElementTests(ManifestParseTestCase):
620 manifest.ToXml() 627 manifest.ToXml()
621 628
622 # Setup target of the include. 629 # Setup target of the include.
623 target = self.tempdir / "target.xml" 630 target = repo_client.topdir / "target.xml"
624 target.write_text("<manifest></manifest>") 631 target.write_text("<manifest></manifest>")
625 632
626 # Include with absolute path. 633 # Include with absolute path.
627 parse(os.path.abspath(target)) 634 parse(str(target.absolute()))
628 635
629 # Include with relative path. 636 # Include with relative path.
630 parse(os.path.relpath(target, self.manifest_dir)) 637 parse(os.path.relpath(str(target), str(repo_client.manifest_dir)))
631 638
632 def test_bad_name_checks(self): 639 def test_bad_name_checks(self, repo_client: RepoClient) -> None:
633 """Check handling of bad name attribute.""" 640 """Check handling of bad name attribute."""
634 641
635 def parse(name): 642 def parse(name: str) -> None:
636 name = self.encodeXmlAttr(name) 643 name = repo_client.encode_xml_attr(name)
637 # Setup target of the include. 644 # Setup target of the include.
638 (self.manifest_dir / "target.xml").write_text( 645 (repo_client.manifest_dir / "target.xml").write_text(
639 f'<manifest><include name="{name}"/></manifest>' 646 f'<manifest><include name="{name}"/></manifest>'
640 ) 647 )
641 648
642 manifest = self.getXmlManifest( 649 manifest = repo_client.get_xml_manifest(
643 """ 650 """
644<manifest> 651<manifest>
645 <remote name="default-remote" fetch="http://localhost" /> 652 <remote name="default-remote" fetch="http://localhost" />
@@ -652,23 +659,23 @@ class IncludeElementTests(ManifestParseTestCase):
652 manifest.ToXml() 659 manifest.ToXml()
653 660
654 # Handle empty name explicitly because a different codepath rejects it. 661 # Handle empty name explicitly because a different codepath rejects it.
655 with self.assertRaises(error.ManifestParseError): 662 with pytest.raises(error.ManifestParseError):
656 parse("") 663 parse("")
657 664
658 for path in INVALID_FS_PATHS: 665 for path in INVALID_FS_PATHS:
659 if not path: 666 if not path:
660 continue 667 continue
661 668
662 with self.assertRaises(error.ManifestInvalidPathError): 669 with pytest.raises(error.ManifestInvalidPathError):
663 parse(path) 670 parse(path)
664 671
665 672
666class ProjectElementTests(ManifestParseTestCase): 673class TestProjectElement:
667 """Tests for <project>.""" 674 """Tests for <project>."""
668 675
669 def test_group(self): 676 def test_group(self, repo_client: RepoClient) -> None:
670 """Check project group settings.""" 677 """Check project group settings."""
671 manifest = self.getXmlManifest( 678 manifest = repo_client.get_xml_manifest(
672 """ 679 """
673<manifest> 680<manifest>
674 <remote name="test-remote" fetch="http://localhost" /> 681 <remote name="test-remote" fetch="http://localhost" />
@@ -678,28 +685,33 @@ class ProjectElementTests(ManifestParseTestCase):
678</manifest> 685</manifest>
679""" 686"""
680 ) 687 )
681 self.assertEqual(len(manifest.projects), 2) 688 assert len(manifest.projects) == 2
682 # Ordering isn't guaranteed. 689 # Ordering isn't guaranteed.
683 result = { 690 result = {
684 manifest.projects[0].name: manifest.projects[0].groups, 691 manifest.projects[0].name: manifest.projects[0].groups,
685 manifest.projects[1].name: manifest.projects[1].groups, 692 manifest.projects[1].name: manifest.projects[1].groups,
686 } 693 }
687 self.assertEqual( 694 assert result["test-name"] == {
688 result["test-name"], {"name:test-name", "all", "path:test-path"} 695 "name:test-name",
689 ) 696 "all",
690 self.assertEqual( 697 "path:test-path",
691 result["extras"], 698 }
692 {"g1", "g2", "name:extras", "all", "path:path"}, 699 assert result["extras"] == {
693 ) 700 "g1",
701 "g2",
702 "name:extras",
703 "all",
704 "path:path",
705 }
694 groupstr = "default,platform-" + platform.system().lower() 706 groupstr = "default,platform-" + platform.system().lower()
695 self.assertEqual(groupstr, manifest.GetManifestGroupsStr()) 707 assert manifest.GetManifestGroupsStr() == groupstr
696 groupstr = "g1,g2,g1" 708 groupstr = "g1,g2,g1"
697 manifest.manifestProject.config.SetString("manifest.groups", groupstr) 709 manifest.manifestProject.config.SetString("manifest.groups", groupstr)
698 self.assertEqual(groupstr, manifest.GetManifestGroupsStr()) 710 assert manifest.GetManifestGroupsStr() == groupstr
699 711
700 def test_set_revision_id(self): 712 def test_set_revision_id(self, repo_client: RepoClient) -> None:
701 """Check setting of project's revisionId.""" 713 """Check setting of project's revisionId."""
702 manifest = self.getXmlManifest( 714 manifest = repo_client.get_xml_manifest(
703 """ 715 """
704<manifest> 716<manifest>
705 <remote name="default-remote" fetch="http://localhost" /> 717 <remote name="default-remote" fetch="http://localhost" />
@@ -708,25 +720,25 @@ class ProjectElementTests(ManifestParseTestCase):
708</manifest> 720</manifest>
709""" 721"""
710 ) 722 )
711 self.assertEqual(len(manifest.projects), 1) 723 assert len(manifest.projects) == 1
712 project = manifest.projects[0] 724 project = manifest.projects[0]
713 project.SetRevisionId("ABCDEF") 725 project.SetRevisionId("ABCDEF")
714 self.assertEqual( 726 assert (
715 sort_attributes(manifest.ToXml().toxml()), 727 sort_attributes(manifest.ToXml().toxml())
716 '<?xml version="1.0" ?><manifest>' 728 == '<?xml version="1.0" ?><manifest>'
717 '<remote fetch="http://localhost" name="default-remote"/>' 729 '<remote fetch="http://localhost" name="default-remote"/>'
718 '<default remote="default-remote" revision="refs/heads/main"/>' 730 '<default remote="default-remote" revision="refs/heads/main"/>'
719 '<project name="test-name" revision="ABCDEF" upstream="refs/heads/main"/>' # noqa: E501 731 '<project name="test-name" revision="ABCDEF" upstream="refs/heads/main"/>' # noqa: E501
720 "</manifest>", 732 "</manifest>"
721 ) 733 )
722 734
723 def test_trailing_slash(self): 735 def test_trailing_slash(self, repo_client: RepoClient) -> None:
724 """Check handling of trailing slashes in attributes.""" 736 """Check handling of trailing slashes in attributes."""
725 737
726 def parse(name, path): 738 def parse(name: str, path: str) -> manifest_xml.XmlManifest:
727 name = self.encodeXmlAttr(name) 739 name = repo_client.encode_xml_attr(name)
728 path = self.encodeXmlAttr(path) 740 path = repo_client.encode_xml_attr(path)
729 return self.getXmlManifest( 741 return repo_client.get_xml_manifest(
730 f""" 742 f"""
731<manifest> 743<manifest>
732 <remote name="default-remote" fetch="http://localhost" /> 744 <remote name="default-remote" fetch="http://localhost" />
@@ -737,48 +749,36 @@ class ProjectElementTests(ManifestParseTestCase):
737 ) 749 )
738 750
739 manifest = parse("a/path/", "foo") 751 manifest = parse("a/path/", "foo")
740 self.assertEqual( 752 assert os.path.normpath(manifest.projects[0].gitdir) == os.path.join(
741 os.path.normpath(manifest.projects[0].gitdir), 753 str(repo_client.topdir), ".repo", "projects", "foo.git"
742 os.path.join(self.tempdir, ".repo", "projects", "foo.git"),
743 ) 754 )
744 self.assertEqual( 755 assert os.path.normpath(manifest.projects[0].objdir) == os.path.join(
745 os.path.normpath(manifest.projects[0].objdir), 756 str(repo_client.topdir), ".repo", "project-objects", "a", "path.git"
746 os.path.join(
747 self.tempdir, ".repo", "project-objects", "a", "path.git"
748 ),
749 ) 757 )
750 758
751 manifest = parse("a/path", "foo/") 759 manifest = parse("a/path", "foo/")
752 self.assertEqual( 760 assert os.path.normpath(manifest.projects[0].gitdir) == os.path.join(
753 os.path.normpath(manifest.projects[0].gitdir), 761 str(repo_client.topdir), ".repo", "projects", "foo.git"
754 os.path.join(self.tempdir, ".repo", "projects", "foo.git"),
755 ) 762 )
756 self.assertEqual( 763 assert os.path.normpath(manifest.projects[0].objdir) == os.path.join(
757 os.path.normpath(manifest.projects[0].objdir), 764 str(repo_client.topdir), ".repo", "project-objects", "a", "path.git"
758 os.path.join(
759 self.tempdir, ".repo", "project-objects", "a", "path.git"
760 ),
761 ) 765 )
762 766
763 manifest = parse("a/path", "foo//////") 767 manifest = parse("a/path", "foo//////")
764 self.assertEqual( 768 assert os.path.normpath(manifest.projects[0].gitdir) == os.path.join(
765 os.path.normpath(manifest.projects[0].gitdir), 769 str(repo_client.topdir), ".repo", "projects", "foo.git"
766 os.path.join(self.tempdir, ".repo", "projects", "foo.git"),
767 ) 770 )
768 self.assertEqual( 771 assert os.path.normpath(manifest.projects[0].objdir) == os.path.join(
769 os.path.normpath(manifest.projects[0].objdir), 772 str(repo_client.topdir), ".repo", "project-objects", "a", "path.git"
770 os.path.join(
771 self.tempdir, ".repo", "project-objects", "a", "path.git"
772 ),
773 ) 773 )
774 774
775 def test_toplevel_path(self): 775 def test_toplevel_path(self, repo_client: RepoClient) -> None:
776 """Check handling of path=. specially.""" 776 """Check handling of path=. specially."""
777 777
778 def parse(name, path): 778 def parse(name: str, path: str) -> manifest_xml.XmlManifest:
779 name = self.encodeXmlAttr(name) 779 name = repo_client.encode_xml_attr(name)
780 path = self.encodeXmlAttr(path) 780 path = repo_client.encode_xml_attr(path)
781 return self.getXmlManifest( 781 return repo_client.get_xml_manifest(
782 f""" 782 f"""
783<manifest> 783<manifest>
784 <remote name="default-remote" fetch="http://localhost" /> 784 <remote name="default-remote" fetch="http://localhost" />
@@ -790,18 +790,19 @@ class ProjectElementTests(ManifestParseTestCase):
790 790
791 for path in (".", "./", ".//", ".///"): 791 for path in (".", "./", ".//", ".///"):
792 manifest = parse("server/path", path) 792 manifest = parse("server/path", path)
793 self.assertEqual( 793 assert os.path.normpath(
794 os.path.normpath(manifest.projects[0].gitdir), 794 manifest.projects[0].gitdir
795 os.path.join(self.tempdir, ".repo", "projects", "..git"), 795 ) == os.path.join(
796 str(repo_client.topdir), ".repo", "projects", "..git"
796 ) 797 )
797 798
798 def test_bad_path_name_checks(self): 799 def test_bad_path_name_checks(self, repo_client: RepoClient) -> None:
799 """Check handling of bad path & name attributes.""" 800 """Check handling of bad path & name attributes."""
800 801
801 def parse(name, path): 802 def parse(name: str, path: str) -> None:
802 name = self.encodeXmlAttr(name) 803 name = repo_client.encode_xml_attr(name)
803 path = self.encodeXmlAttr(path) 804 path = repo_client.encode_xml_attr(path)
804 manifest = self.getXmlManifest( 805 manifest = repo_client.get_xml_manifest(
805 f""" 806 f"""
806<manifest> 807<manifest>
807 <remote name="default-remote" fetch="http://localhost" /> 808 <remote name="default-remote" fetch="http://localhost" />
@@ -818,28 +819,28 @@ class ProjectElementTests(ManifestParseTestCase):
818 819
819 # Handle empty name explicitly because a different codepath rejects it. 820 # Handle empty name explicitly because a different codepath rejects it.
820 # Empty path is OK because it defaults to the name field. 821 # Empty path is OK because it defaults to the name field.
821 with self.assertRaises(error.ManifestParseError): 822 with pytest.raises(error.ManifestParseError):
822 parse("", "ok") 823 parse("", "ok")
823 824
824 for path in INVALID_FS_PATHS: 825 for path in INVALID_FS_PATHS:
825 if not path or path.endswith("/") or path.endswith(os.path.sep): 826 if not path or path.endswith("/") or path.endswith(os.path.sep):
826 continue 827 continue
827 828
828 with self.assertRaises(error.ManifestInvalidPathError): 829 with pytest.raises(error.ManifestInvalidPathError):
829 parse(path, "ok") 830 parse(path, "ok")
830 831
831 # We have a dedicated test for path=".". 832 # We have a dedicated test for path=".".
832 if path not in {"."}: 833 if path not in {"."}:
833 with self.assertRaises(error.ManifestInvalidPathError): 834 with pytest.raises(error.ManifestInvalidPathError):
834 parse("ok", path) 835 parse("ok", path)
835 836
836 837
837class SuperProjectElementTests(ManifestParseTestCase): 838class TestSuperProjectElement:
838 """Tests for <superproject>.""" 839 """Tests for <superproject>."""
839 840
840 def test_superproject(self): 841 def test_superproject(self, repo_client: RepoClient) -> None:
841 """Check superproject settings.""" 842 """Check superproject settings."""
842 manifest = self.getXmlManifest( 843 manifest = repo_client.get_xml_manifest(
843 """ 844 """
844<manifest> 845<manifest>
845 <remote name="test-remote" fetch="http://localhost" /> 846 <remote name="test-remote" fetch="http://localhost" />
@@ -848,25 +849,24 @@ class SuperProjectElementTests(ManifestParseTestCase):
848</manifest> 849</manifest>
849""" 850"""
850 ) 851 )
851 self.assertEqual(manifest.superproject.name, "superproject") 852 assert manifest.superproject.name == "superproject"
852 self.assertEqual(manifest.superproject.remote.name, "test-remote") 853 assert manifest.superproject.remote.name == "test-remote"
853 self.assertEqual( 854 assert (
854 manifest.superproject.remote.url, "http://localhost/superproject" 855 manifest.superproject.remote.url == "http://localhost/superproject"
855 ) 856 )
856 self.assertEqual(manifest.superproject.revision, "refs/heads/main") 857 assert manifest.superproject.revision == "refs/heads/main"
857 self.assertEqual( 858 assert (
858 sort_attributes(manifest.ToXml().toxml()), 859 sort_attributes(manifest.ToXml().toxml())
859 '<?xml version="1.0" ?><manifest>' 860 == '<?xml version="1.0" ?><manifest>'
860 '<remote fetch="http://localhost" name="test-remote"/>' 861 '<remote fetch="http://localhost" name="test-remote"/>'
861 '<default remote="test-remote" revision="refs/heads/main"/>' 862 '<default remote="test-remote" revision="refs/heads/main"/>'
862 '<superproject name="superproject"/>' 863 '<superproject name="superproject"/>'
863 "</manifest>", 864 "</manifest>"
864 ) 865 )
865 866
866 def test_superproject_revision(self): 867 def test_superproject_revision(self, repo_client: RepoClient) -> None:
867 """Check superproject settings with a different revision attribute""" 868 """Check superproject settings with a different revision attribute"""
868 self.maxDiff = None 869 manifest = repo_client.get_xml_manifest(
869 manifest = self.getXmlManifest(
870 """ 870 """
871<manifest> 871<manifest>
872 <remote name="test-remote" fetch="http://localhost" /> 872 <remote name="test-remote" fetch="http://localhost" />
@@ -875,25 +875,26 @@ class SuperProjectElementTests(ManifestParseTestCase):
875</manifest> 875</manifest>
876""" 876"""
877 ) 877 )
878 self.assertEqual(manifest.superproject.name, "superproject") 878 assert manifest.superproject.name == "superproject"
879 self.assertEqual(manifest.superproject.remote.name, "test-remote") 879 assert manifest.superproject.remote.name == "test-remote"
880 self.assertEqual( 880 assert (
881 manifest.superproject.remote.url, "http://localhost/superproject" 881 manifest.superproject.remote.url == "http://localhost/superproject"
882 ) 882 )
883 self.assertEqual(manifest.superproject.revision, "refs/heads/stable") 883 assert manifest.superproject.revision == "refs/heads/stable"
884 self.assertEqual( 884 assert (
885 sort_attributes(manifest.ToXml().toxml()), 885 sort_attributes(manifest.ToXml().toxml())
886 '<?xml version="1.0" ?><manifest>' 886 == '<?xml version="1.0" ?><manifest>'
887 '<remote fetch="http://localhost" name="test-remote"/>' 887 '<remote fetch="http://localhost" name="test-remote"/>'
888 '<default remote="test-remote" revision="refs/heads/main"/>' 888 '<default remote="test-remote" revision="refs/heads/main"/>'
889 '<superproject name="superproject" revision="refs/heads/stable"/>' 889 '<superproject name="superproject" revision="refs/heads/stable"/>'
890 "</manifest>", 890 "</manifest>"
891 ) 891 )
892 892
893 def test_superproject_revision_default_negative(self): 893 def test_superproject_revision_default_negative(
894 self, repo_client: RepoClient
895 ) -> None:
894 """Check superproject settings with a same revision attribute""" 896 """Check superproject settings with a same revision attribute"""
895 self.maxDiff = None 897 manifest = repo_client.get_xml_manifest(
896 manifest = self.getXmlManifest(
897 """ 898 """
898<manifest> 899<manifest>
899 <remote name="test-remote" fetch="http://localhost" /> 900 <remote name="test-remote" fetch="http://localhost" />
@@ -902,51 +903,53 @@ class SuperProjectElementTests(ManifestParseTestCase):
902</manifest> 903</manifest>
903""" 904"""
904 ) 905 )
905 self.assertEqual(manifest.superproject.name, "superproject") 906 assert manifest.superproject.name == "superproject"
906 self.assertEqual(manifest.superproject.remote.name, "test-remote") 907 assert manifest.superproject.remote.name == "test-remote"
907 self.assertEqual( 908 assert (
908 manifest.superproject.remote.url, "http://localhost/superproject" 909 manifest.superproject.remote.url == "http://localhost/superproject"
909 ) 910 )
910 self.assertEqual(manifest.superproject.revision, "refs/heads/stable") 911 assert manifest.superproject.revision == "refs/heads/stable"
911 self.assertEqual( 912 assert (
912 sort_attributes(manifest.ToXml().toxml()), 913 sort_attributes(manifest.ToXml().toxml())
913 '<?xml version="1.0" ?><manifest>' 914 == '<?xml version="1.0" ?><manifest>'
914 '<remote fetch="http://localhost" name="test-remote"/>' 915 '<remote fetch="http://localhost" name="test-remote"/>'
915 '<default remote="test-remote" revision="refs/heads/stable"/>' 916 '<default remote="test-remote" revision="refs/heads/stable"/>'
916 '<superproject name="superproject"/>' 917 '<superproject name="superproject"/>'
917 "</manifest>", 918 "</manifest>"
918 ) 919 )
919 920
920 def test_superproject_revision_remote(self): 921 def test_superproject_revision_remote(
922 self, repo_client: RepoClient
923 ) -> None:
921 """Check superproject settings with a same revision attribute""" 924 """Check superproject settings with a same revision attribute"""
922 self.maxDiff = None 925 manifest = repo_client.get_xml_manifest(
923 manifest = self.getXmlManifest(
924 """ 926 """
925<manifest> 927<manifest>
926 <remote name="test-remote" fetch="http://localhost" revision="refs/heads/main" /> 928 <remote name="test-remote" fetch="http://localhost"
929 revision="refs/heads/main" />
927 <default remote="test-remote" /> 930 <default remote="test-remote" />
928 <superproject name="superproject" revision="refs/heads/stable" /> 931 <superproject name="superproject" revision="refs/heads/stable" />
929</manifest> 932</manifest>
930""" # noqa: E501 933"""
931 ) 934 )
932 self.assertEqual(manifest.superproject.name, "superproject") 935 assert manifest.superproject.name == "superproject"
933 self.assertEqual(manifest.superproject.remote.name, "test-remote") 936 assert manifest.superproject.remote.name == "test-remote"
934 self.assertEqual( 937 assert (
935 manifest.superproject.remote.url, "http://localhost/superproject" 938 manifest.superproject.remote.url == "http://localhost/superproject"
936 ) 939 )
937 self.assertEqual(manifest.superproject.revision, "refs/heads/stable") 940 assert manifest.superproject.revision == "refs/heads/stable"
938 self.assertEqual( 941 assert (
939 sort_attributes(manifest.ToXml().toxml()), 942 sort_attributes(manifest.ToXml().toxml())
940 '<?xml version="1.0" ?><manifest>' 943 == '<?xml version="1.0" ?><manifest>'
941 '<remote fetch="http://localhost" name="test-remote" revision="refs/heads/main"/>' # noqa: E501 944 '<remote fetch="http://localhost" name="test-remote" revision="refs/heads/main"/>' # noqa: E501
942 '<default remote="test-remote"/>' 945 '<default remote="test-remote"/>'
943 '<superproject name="superproject" revision="refs/heads/stable"/>' 946 '<superproject name="superproject" revision="refs/heads/stable"/>'
944 "</manifest>", 947 "</manifest>"
945 ) 948 )
946 949
947 def test_remote(self): 950 def test_remote(self, repo_client: RepoClient) -> None:
948 """Check superproject settings with a remote.""" 951 """Check superproject settings with a remote."""
949 manifest = self.getXmlManifest( 952 manifest = repo_client.get_xml_manifest(
950 """ 953 """
951<manifest> 954<manifest>
952 <remote name="default-remote" fetch="http://localhost" /> 955 <remote name="default-remote" fetch="http://localhost" />
@@ -956,28 +959,26 @@ class SuperProjectElementTests(ManifestParseTestCase):
956</manifest> 959</manifest>
957""" 960"""
958 ) 961 )
959 self.assertEqual(manifest.superproject.name, "platform/superproject") 962 assert manifest.superproject.name == "platform/superproject"
960 self.assertEqual( 963 assert manifest.superproject.remote.name == "superproject-remote"
961 manifest.superproject.remote.name, "superproject-remote" 964 assert (
965 manifest.superproject.remote.url
966 == "http://localhost/platform/superproject"
962 ) 967 )
963 self.assertEqual( 968 assert manifest.superproject.revision == "refs/heads/main"
964 manifest.superproject.remote.url, 969 assert (
965 "http://localhost/platform/superproject", 970 sort_attributes(manifest.ToXml().toxml())
966 ) 971 == '<?xml version="1.0" ?><manifest>'
967 self.assertEqual(manifest.superproject.revision, "refs/heads/main")
968 self.assertEqual(
969 sort_attributes(manifest.ToXml().toxml()),
970 '<?xml version="1.0" ?><manifest>'
971 '<remote fetch="http://localhost" name="default-remote"/>' 972 '<remote fetch="http://localhost" name="default-remote"/>'
972 '<remote fetch="http://localhost" name="superproject-remote"/>' 973 '<remote fetch="http://localhost" name="superproject-remote"/>'
973 '<default remote="default-remote" revision="refs/heads/main"/>' 974 '<default remote="default-remote" revision="refs/heads/main"/>'
974 '<superproject name="platform/superproject" remote="superproject-remote"/>' # noqa: E501 975 '<superproject name="platform/superproject" remote="superproject-remote"/>' # noqa: E501
975 "</manifest>", 976 "</manifest>"
976 ) 977 )
977 978
978 def test_defalut_remote(self): 979 def test_default_remote(self, repo_client: RepoClient) -> None:
979 """Check superproject settings with a default remote.""" 980 """Check superproject settings with a default remote."""
980 manifest = self.getXmlManifest( 981 manifest = repo_client.get_xml_manifest(
981 """ 982 """
982<manifest> 983<manifest>
983 <remote name="default-remote" fetch="http://localhost" /> 984 <remote name="default-remote" fetch="http://localhost" />
@@ -986,62 +987,61 @@ class SuperProjectElementTests(ManifestParseTestCase):
986</manifest> 987</manifest>
987""" 988"""
988 ) 989 )
989 self.assertEqual(manifest.superproject.name, "superproject") 990 assert manifest.superproject.name == "superproject"
990 self.assertEqual(manifest.superproject.remote.name, "default-remote") 991 assert manifest.superproject.remote.name == "default-remote"
991 self.assertEqual(manifest.superproject.revision, "refs/heads/main") 992 assert manifest.superproject.revision == "refs/heads/main"
992 self.assertEqual( 993 assert (
993 sort_attributes(manifest.ToXml().toxml()), 994 sort_attributes(manifest.ToXml().toxml())
994 '<?xml version="1.0" ?><manifest>' 995 == '<?xml version="1.0" ?><manifest>'
995 '<remote fetch="http://localhost" name="default-remote"/>' 996 '<remote fetch="http://localhost" name="default-remote"/>'
996 '<default remote="default-remote" revision="refs/heads/main"/>' 997 '<default remote="default-remote" revision="refs/heads/main"/>'
997 '<superproject name="superproject"/>' 998 '<superproject name="superproject"/>'
998 "</manifest>", 999 "</manifest>"
999 ) 1000 )
1000 1001
1001 1002
1002class ContactinfoElementTests(ManifestParseTestCase): 1003class TestContactinfoElement:
1003 """Tests for <contactinfo>.""" 1004 """Tests for <contactinfo>."""
1004 1005
1005 def test_contactinfo(self): 1006 def test_contactinfo(self, repo_client: RepoClient) -> None:
1006 """Check contactinfo settings.""" 1007 """Check contactinfo settings."""
1007 bugurl = "http://localhost/contactinfo" 1008 bugurl = "http://localhost/contactinfo"
1008 manifest = self.getXmlManifest( 1009 manifest = repo_client.get_xml_manifest(
1009 f""" 1010 f"""
1010<manifest> 1011<manifest>
1011 <contactinfo bugurl="{bugurl}"/> 1012 <contactinfo bugurl="{bugurl}"/>
1012</manifest> 1013</manifest>
1013""" 1014"""
1014 ) 1015 )
1015 self.assertEqual(manifest.contactinfo.bugurl, bugurl) 1016 assert manifest.contactinfo.bugurl == bugurl
1016 self.assertEqual( 1017 assert (
1017 manifest.ToXml().toxml(), 1018 manifest.ToXml().toxml() == '<?xml version="1.0" ?><manifest>'
1018 '<?xml version="1.0" ?><manifest>'
1019 f'<contactinfo bugurl="{bugurl}"/>' 1019 f'<contactinfo bugurl="{bugurl}"/>'
1020 "</manifest>", 1020 "</manifest>"
1021 ) 1021 )
1022 1022
1023 1023
1024class DefaultElementTests(ManifestParseTestCase): 1024class TestDefaultElement:
1025 """Tests for <default>.""" 1025 """Tests for <default>."""
1026 1026
1027 def test_default(self): 1027 def test_default(self) -> None:
1028 """Check default settings.""" 1028 """Check default settings."""
1029 a = manifest_xml._Default() 1029 a = manifest_xml._Default()
1030 a.revisionExpr = "foo" 1030 a.revisionExpr = "foo"
1031 a.remote = manifest_xml._XmlRemote(name="remote") 1031 a.remote = manifest_xml._XmlRemote(name="remote")
1032 b = manifest_xml._Default() 1032 b = manifest_xml._Default()
1033 b.revisionExpr = "bar" 1033 b.revisionExpr = "bar"
1034 self.assertEqual(a, a) 1034 assert a == a
1035 self.assertNotEqual(a, b) 1035 assert a != b
1036 self.assertNotEqual(b, a.remote) 1036 assert b != a.remote
1037 self.assertNotEqual(a, 123) 1037 assert a != 123
1038 self.assertNotEqual(a, None) 1038 assert a is not None
1039 1039
1040 1040
1041class RemoteElementTests(ManifestParseTestCase): 1041class TestRemoteElement:
1042 """Tests for <remote>.""" 1042 """Tests for <remote>."""
1043 1043
1044 def test_remote(self): 1044 def test_remote(self) -> None:
1045 """Check remote settings.""" 1045 """Check remote settings."""
1046 a = manifest_xml._XmlRemote(name="foo") 1046 a = manifest_xml._XmlRemote(name="foo")
1047 a.AddAnnotation("key1", "value1", "true") 1047 a.AddAnnotation("key1", "value1", "true")
@@ -1051,20 +1051,21 @@ class RemoteElementTests(ManifestParseTestCase):
1051 c.AddAnnotation("key1", "value2", "true") 1051 c.AddAnnotation("key1", "value2", "true")
1052 d = manifest_xml._XmlRemote(name="foo") 1052 d = manifest_xml._XmlRemote(name="foo")
1053 d.AddAnnotation("key1", "value1", "false") 1053 d.AddAnnotation("key1", "value1", "false")
1054 self.assertEqual(a, a) 1054 assert a == a
1055 self.assertNotEqual(a, b) 1055 assert a != b
1056 self.assertNotEqual(a, c) 1056 assert a != c
1057 self.assertNotEqual(a, d) 1057 assert a != d
1058 self.assertNotEqual(a, manifest_xml._Default()) 1058 assert a != manifest_xml._Default()
1059 self.assertNotEqual(a, 123) 1059 assert a != 123
1060 self.assertNotEqual(a, None) 1060 assert a is not None
1061 1061
1062 1062
1063class RemoveProjectElementTests(ManifestParseTestCase): 1063class TestRemoveProjectElement:
1064 """Tests for <remove-project>.""" 1064 """Tests for <remove-project>."""
1065 1065
1066 def test_remove_one_project(self): 1066 def test_remove_one_project(self, repo_client: RepoClient) -> None:
1067 manifest = self.getXmlManifest( 1067 """Check removal of a single project."""
1068 manifest = repo_client.get_xml_manifest(
1068 """ 1069 """
1069<manifest> 1070<manifest>
1070 <remote name="default-remote" fetch="http://localhost" /> 1071 <remote name="default-remote" fetch="http://localhost" />
@@ -1074,10 +1075,13 @@ class RemoveProjectElementTests(ManifestParseTestCase):
1074</manifest> 1075</manifest>
1075""" 1076"""
1076 ) 1077 )
1077 self.assertEqual(manifest.projects, []) 1078 assert manifest.projects == []
1078 1079
1079 def test_remove_one_project_one_remains(self): 1080 def test_remove_one_project_one_remains(
1080 manifest = self.getXmlManifest( 1081 self, repo_client: RepoClient
1082 ) -> None:
1083 """Check removal of one project while another remains."""
1084 manifest = repo_client.get_xml_manifest(
1081 """ 1085 """
1082<manifest> 1086<manifest>
1083 <remote name="default-remote" fetch="http://localhost" /> 1087 <remote name="default-remote" fetch="http://localhost" />
@@ -1089,24 +1093,30 @@ class RemoveProjectElementTests(ManifestParseTestCase):
1089""" 1093"""
1090 ) 1094 )
1091 1095
1092 self.assertEqual(len(manifest.projects), 1) 1096 assert len(manifest.projects) == 1
1093 self.assertEqual(manifest.projects[0].name, "yourproject") 1097 assert manifest.projects[0].name == "yourproject"
1094 1098
1095 def test_remove_one_project_doesnt_exist(self): 1099 def test_remove_one_project_doesnt_exist(
1096 with self.assertRaises(manifest_xml.ManifestParseError): 1100 self, repo_client: RepoClient
1097 manifest = self.getXmlManifest( 1101 ) -> None:
1098 """ 1102 """Check removal of non-existent project fails."""
1103 manifest = repo_client.get_xml_manifest(
1104 """
1099<manifest> 1105<manifest>
1100 <remote name="default-remote" fetch="http://localhost" /> 1106 <remote name="default-remote" fetch="http://localhost" />
1101 <default remote="default-remote" revision="refs/heads/main" /> 1107 <default remote="default-remote" revision="refs/heads/main" />
1102 <remove-project name="myproject" /> 1108 <remove-project name="myproject" />
1103</manifest> 1109</manifest>
1104""" 1110"""
1105 ) 1111 )
1112 with pytest.raises(error.ManifestParseError):
1106 manifest.projects 1113 manifest.projects
1107 1114
1108 def test_remove_one_optional_project_doesnt_exist(self): 1115 def test_remove_one_optional_project_doesnt_exist(
1109 manifest = self.getXmlManifest( 1116 self, repo_client: RepoClient
1117 ) -> None:
1118 """Check optional removal of non-existent project passes."""
1119 manifest = repo_client.get_xml_manifest(
1110 """ 1120 """
1111<manifest> 1121<manifest>
1112 <remote name="default-remote" fetch="http://localhost" /> 1122 <remote name="default-remote" fetch="http://localhost" />
@@ -1115,10 +1125,11 @@ class RemoveProjectElementTests(ManifestParseTestCase):
1115</manifest> 1125</manifest>
1116""" 1126"""
1117 ) 1127 )
1118 self.assertEqual(manifest.projects, []) 1128 assert manifest.projects == []
1119 1129
1120 def test_remove_using_path_attrib(self): 1130 def test_remove_using_path_attrib(self, repo_client: RepoClient) -> None:
1121 manifest = self.getXmlManifest( 1131 """Check removal using name and path attributes."""
1132 manifest = repo_client.get_xml_manifest(
1122 """ 1133 """
1123<manifest> 1134<manifest>
1124 <remote name="default-remote" fetch="http://localhost" /> 1135 <remote name="default-remote" fetch="http://localhost" />
@@ -1145,18 +1156,21 @@ class RemoveProjectElementTests(ManifestParseTestCase):
1145 for proj in manifest.projects: 1156 for proj in manifest.projects:
1146 if proj.name == "project1": 1157 if proj.name == "project1":
1147 found_proj1_path1 = True 1158 found_proj1_path1 = True
1148 self.assertEqual(proj.relpath, "tests/path1") 1159 assert proj.relpath == "tests/path1"
1149 if proj.name == "project2": 1160 if proj.name == "project2":
1150 found_proj2 = True 1161 found_proj2 = True
1151 self.assertNotEqual(proj.name, "project3") 1162 assert proj.name != "project3"
1152 self.assertNotEqual(proj.name, "project4") 1163 assert proj.name != "project4"
1153 self.assertNotEqual(proj.name, "project5") 1164 assert proj.name != "project5"
1154 self.assertNotEqual(proj.name, "project6") 1165 assert proj.name != "project6"
1155 self.assertTrue(found_proj1_path1) 1166 assert found_proj1_path1
1156 self.assertTrue(found_proj2) 1167 assert found_proj2
1157 1168
1158 def test_base_revision_checks_on_patching(self): 1169 def test_base_revision_checks_on_patching(
1159 manifest_fail_wrong_tag = self.getXmlManifest( 1170 self, repo_client: RepoClient
1171 ) -> None:
1172 """Check base-rev validation during patching."""
1173 manifest_fail_wrong_tag = repo_client.get_xml_manifest(
1160 """ 1174 """
1161<manifest> 1175<manifest>
1162 <remote name="default-remote" fetch="http://localhost" /> 1176 <remote name="default-remote" fetch="http://localhost" />
@@ -1166,10 +1180,10 @@ class RemoveProjectElementTests(ManifestParseTestCase):
1166</manifest> 1180</manifest>
1167""" 1181"""
1168 ) 1182 )
1169 with self.assertRaises(error.ManifestParseError): 1183 with pytest.raises(error.ManifestParseError):
1170 manifest_fail_wrong_tag.ToXml() 1184 manifest_fail_wrong_tag.ToXml()
1171 1185
1172 manifest_fail_remove = self.getXmlManifest( 1186 manifest_fail_remove = repo_client.get_xml_manifest(
1173 """ 1187 """
1174<manifest> 1188<manifest>
1175 <remote name="default-remote" fetch="http://localhost" /> 1189 <remote name="default-remote" fetch="http://localhost" />
@@ -1179,10 +1193,10 @@ class RemoveProjectElementTests(ManifestParseTestCase):
1179</manifest> 1193</manifest>
1180""" 1194"""
1181 ) 1195 )
1182 with self.assertRaises(error.ManifestParseError): 1196 with pytest.raises(error.ManifestParseError):
1183 manifest_fail_remove.ToXml() 1197 manifest_fail_remove.ToXml()
1184 1198
1185 manifest_fail_extend = self.getXmlManifest( 1199 manifest_fail_extend = repo_client.get_xml_manifest(
1186 """ 1200 """
1187<manifest> 1201<manifest>
1188 <remote name="default-remote" fetch="http://localhost" /> 1202 <remote name="default-remote" fetch="http://localhost" />
@@ -1192,10 +1206,10 @@ class RemoveProjectElementTests(ManifestParseTestCase):
1192</manifest> 1206</manifest>
1193""" 1207"""
1194 ) 1208 )
1195 with self.assertRaises(error.ManifestParseError): 1209 with pytest.raises(error.ManifestParseError):
1196 manifest_fail_extend.ToXml() 1210 manifest_fail_extend.ToXml()
1197 1211
1198 manifest_fail_unknown = self.getXmlManifest( 1212 manifest_fail_unknown = repo_client.get_xml_manifest(
1199 """ 1213 """
1200<manifest> 1214<manifest>
1201 <remote name="default-remote" fetch="http://localhost" /> 1215 <remote name="default-remote" fetch="http://localhost" />
@@ -1205,10 +1219,10 @@ class RemoveProjectElementTests(ManifestParseTestCase):
1205</manifest> 1219</manifest>
1206""" 1220"""
1207 ) 1221 )
1208 with self.assertRaises(error.ManifestParseError): 1222 with pytest.raises(error.ManifestParseError):
1209 manifest_fail_unknown.ToXml() 1223 manifest_fail_unknown.ToXml()
1210 1224
1211 manifest_ok = self.getXmlManifest( 1225 manifest_ok = repo_client.get_xml_manifest(
1212 """ 1226 """
1213<manifest> 1227<manifest>
1214 <remote name="default-remote" fetch="http://localhost" /> 1228 <remote name="default-remote" fetch="http://localhost" />
@@ -1234,18 +1248,21 @@ class RemoveProjectElementTests(ManifestParseTestCase):
1234 found_proj2 = True 1248 found_proj2 = True
1235 if proj.name == "project3": 1249 if proj.name == "project3":
1236 found_proj3 = True 1250 found_proj3 = True
1237 self.assertNotEqual(proj.name, "project1") 1251 assert proj.name != "project1"
1238 self.assertNotEqual(proj.name, "project4") 1252 assert proj.name != "project4"
1239 self.assertTrue(found_proj2) 1253 assert found_proj2
1240 self.assertTrue(found_proj3) 1254 assert found_proj3
1241 self.assertTrue(len(manifest_ok.projects) == 2) 1255 assert len(manifest_ok.projects) == 2
1242 1256
1243 1257
1244class ExtendProjectElementTests(ManifestParseTestCase): 1258class TestExtendProjectElement:
1245 """Tests for <extend-project>.""" 1259 """Tests for <extend-project>."""
1246 1260
1247 def test_extend_project_dest_path_single_match(self): 1261 def test_extend_project_dest_path_single_match(
1248 manifest = self.getXmlManifest( 1262 self, repo_client: RepoClient
1263 ) -> None:
1264 """Check dest-path when single match exists."""
1265 manifest = repo_client.get_xml_manifest(
1249 """ 1266 """
1250<manifest> 1267<manifest>
1251 <remote name="default-remote" fetch="http://localhost" /> 1268 <remote name="default-remote" fetch="http://localhost" />
@@ -1255,13 +1272,15 @@ class ExtendProjectElementTests(ManifestParseTestCase):
1255</manifest> 1272</manifest>
1256""" 1273"""
1257 ) 1274 )
1258 self.assertEqual(len(manifest.projects), 1) 1275 assert len(manifest.projects) == 1
1259 self.assertEqual(manifest.projects[0].relpath, "bar") 1276 assert manifest.projects[0].relpath == "bar"
1260 1277
1261 def test_extend_project_dest_path_multi_match(self): 1278 def test_extend_project_dest_path_multi_match(
1262 with self.assertRaises(manifest_xml.ManifestParseError): 1279 self, repo_client: RepoClient
1263 manifest = self.getXmlManifest( 1280 ) -> None:
1264 """ 1281 """Check dest-path when multiple matches exist fails."""
1282 manifest = repo_client.get_xml_manifest(
1283 """
1265<manifest> 1284<manifest>
1266 <remote name="default-remote" fetch="http://localhost" /> 1285 <remote name="default-remote" fetch="http://localhost" />
1267 <default remote="default-remote" revision="refs/heads/main" /> 1286 <default remote="default-remote" revision="refs/heads/main" />
@@ -1270,11 +1289,15 @@ class ExtendProjectElementTests(ManifestParseTestCase):
1270 <extend-project name="myproject" dest-path="bar" /> 1289 <extend-project name="myproject" dest-path="bar" />
1271</manifest> 1290</manifest>
1272""" 1291"""
1273 ) 1292 )
1293 with pytest.raises(error.ManifestParseError):
1274 manifest.projects 1294 manifest.projects
1275 1295
1276 def test_extend_project_dest_path_multi_match_path_specified(self): 1296 def test_extend_project_dest_path_multi_match_path_specified(
1277 manifest = self.getXmlManifest( 1297 self, repo_client: RepoClient
1298 ) -> None:
1299 """Check dest-path when path is specified for multi-match."""
1300 manifest = repo_client.get_xml_manifest(
1278 """ 1301 """
1279<manifest> 1302<manifest>
1280 <remote name="default-remote" fetch="http://localhost" /> 1303 <remote name="default-remote" fetch="http://localhost" />
@@ -1285,29 +1308,32 @@ class ExtendProjectElementTests(ManifestParseTestCase):
1285</manifest> 1308</manifest>
1286""" 1309"""
1287 ) 1310 )
1288 self.assertEqual(len(manifest.projects), 2) 1311 assert len(manifest.projects) == 2
1289 if manifest.projects[0].relpath == "y": 1312 if manifest.projects[0].relpath == "y":
1290 self.assertEqual(manifest.projects[1].relpath, "bar") 1313 assert manifest.projects[1].relpath == "bar"
1291 else: 1314 else:
1292 self.assertEqual(manifest.projects[0].relpath, "bar") 1315 assert manifest.projects[0].relpath == "bar"
1293 self.assertEqual(manifest.projects[1].relpath, "y") 1316 assert manifest.projects[1].relpath == "y"
1294 1317
1295 def test_extend_project_dest_branch(self): 1318 def test_extend_project_dest_branch(self, repo_client: RepoClient) -> None:
1296 manifest = self.getXmlManifest( 1319 """Check dest-branch update via extend-project."""
1320 manifest = repo_client.get_xml_manifest(
1297 """ 1321 """
1298<manifest> 1322<manifest>
1299 <remote name="default-remote" fetch="http://localhost" /> 1323 <remote name="default-remote" fetch="http://localhost" />
1300 <default remote="default-remote" revision="refs/heads/main" dest-branch="foo" /> 1324 <default remote="default-remote" revision="refs/heads/main"
1325 dest-branch="foo" />
1301 <project name="myproject" /> 1326 <project name="myproject" />
1302 <extend-project name="myproject" dest-branch="bar" /> 1327 <extend-project name="myproject" dest-branch="bar" />
1303</manifest> 1328</manifest>
1304""" # noqa: E501 1329"""
1305 ) 1330 )
1306 self.assertEqual(len(manifest.projects), 1) 1331 assert len(manifest.projects) == 1
1307 self.assertEqual(manifest.projects[0].dest_branch, "bar") 1332 assert manifest.projects[0].dest_branch == "bar"
1308 1333
1309 def test_extend_project_upstream(self): 1334 def test_extend_project_upstream(self, repo_client: RepoClient) -> None:
1310 manifest = self.getXmlManifest( 1335 """Check upstream update via extend-project."""
1336 manifest = repo_client.get_xml_manifest(
1311 """ 1337 """
1312<manifest> 1338<manifest>
1313 <remote name="default-remote" fetch="http://localhost" /> 1339 <remote name="default-remote" fetch="http://localhost" />
@@ -1317,11 +1343,12 @@ class ExtendProjectElementTests(ManifestParseTestCase):
1317</manifest> 1343</manifest>
1318""" 1344"""
1319 ) 1345 )
1320 self.assertEqual(len(manifest.projects), 1) 1346 assert len(manifest.projects) == 1
1321 self.assertEqual(manifest.projects[0].upstream, "bar") 1347 assert manifest.projects[0].upstream == "bar"
1322 1348
1323 def test_extend_project_copyfiles(self): 1349 def test_extend_project_copyfiles(self, repo_client: RepoClient) -> None:
1324 manifest = self.getXmlManifest( 1350 """Check copyfile addition via extend-project."""
1351 manifest = repo_client.get_xml_manifest(
1325 """ 1352 """
1326<manifest> 1353<manifest>
1327 <remote name="default-remote" fetch="http://localhost" /> 1354 <remote name="default-remote" fetch="http://localhost" />
@@ -1333,21 +1360,24 @@ class ExtendProjectElementTests(ManifestParseTestCase):
1333</manifest> 1360</manifest>
1334""" 1361"""
1335 ) 1362 )
1336 self.assertEqual(list(manifest.projects[0].copyfiles)[0].src, "foo") 1363 assert list(manifest.projects[0].copyfiles)[0].src == "foo"
1337 self.assertEqual(list(manifest.projects[0].copyfiles)[0].dest, "bar") 1364 assert list(manifest.projects[0].copyfiles)[0].dest == "bar"
1338 self.assertEqual( 1365 assert (
1339 sort_attributes(manifest.ToXml().toxml()), 1366 sort_attributes(manifest.ToXml().toxml())
1340 '<?xml version="1.0" ?><manifest>' 1367 == '<?xml version="1.0" ?><manifest>'
1341 '<remote fetch="http://localhost" name="default-remote"/>' 1368 '<remote fetch="http://localhost" name="default-remote"/>'
1342 '<default remote="default-remote" revision="refs/heads/main"/>' 1369 '<default remote="default-remote" revision="refs/heads/main"/>'
1343 '<project name="myproject">' 1370 '<project name="myproject">'
1344 '<copyfile dest="bar" src="foo"/>' 1371 '<copyfile dest="bar" src="foo"/>'
1345 "</project>" 1372 "</project>"
1346 "</manifest>", 1373 "</manifest>"
1347 ) 1374 )
1348 1375
1349 def test_extend_project_duplicate_copyfiles(self): 1376 def test_extend_project_duplicate_copyfiles(
1350 root_m = self.manifest_dir / "root.xml" 1377 self, repo_client: RepoClient
1378 ) -> None:
1379 """Check duplicate copyfile handling in includes."""
1380 root_m = repo_client.manifest_dir / "root.xml"
1351 root_m.write_text( 1381 root_m.write_text(
1352 """ 1382 """
1353<manifest> 1383<manifest>
@@ -1359,21 +1389,21 @@ class ExtendProjectElementTests(ManifestParseTestCase):
1359</manifest> 1389</manifest>
1360""" 1390"""
1361 ) 1391 )
1362 (self.manifest_dir / "man1.xml").write_text( 1392 (repo_client.manifest_dir / "man1.xml").write_text(
1363 """ 1393 """
1364<manifest> 1394<manifest>
1365 <include name="common.xml" /> 1395 <include name="common.xml" />
1366</manifest> 1396</manifest>
1367""" 1397"""
1368 ) 1398 )
1369 (self.manifest_dir / "man2.xml").write_text( 1399 (repo_client.manifest_dir / "man2.xml").write_text(
1370 """ 1400 """
1371<manifest> 1401<manifest>
1372 <include name="common.xml" /> 1402 <include name="common.xml" />
1373</manifest> 1403</manifest>
1374""" 1404"""
1375 ) 1405 )
1376 (self.manifest_dir / "common.xml").write_text( 1406 (repo_client.manifest_dir / "common.xml").write_text(
1377 """ 1407 """
1378<manifest> 1408<manifest>
1379 <extend-project name="myproject"> 1409 <extend-project name="myproject">
@@ -1382,13 +1412,16 @@ class ExtendProjectElementTests(ManifestParseTestCase):
1382</manifest> 1412</manifest>
1383""" 1413"""
1384 ) 1414 )
1385 manifest = manifest_xml.XmlManifest(str(self.repodir), str(root_m)) 1415 manifest = manifest_xml.XmlManifest(
1386 self.assertEqual(len(manifest.projects[0].copyfiles), 1) 1416 str(repo_client.repodir), str(root_m)
1387 self.assertEqual(list(manifest.projects[0].copyfiles)[0].src, "foo") 1417 )
1388 self.assertEqual(list(manifest.projects[0].copyfiles)[0].dest, "bar") 1418 assert len(manifest.projects[0].copyfiles) == 1
1419 assert list(manifest.projects[0].copyfiles)[0].src == "foo"
1420 assert list(manifest.projects[0].copyfiles)[0].dest == "bar"
1389 1421
1390 def test_extend_project_linkfiles(self): 1422 def test_extend_project_linkfiles(self, repo_client: RepoClient) -> None:
1391 manifest = self.getXmlManifest( 1423 """Check linkfile addition via extend-project."""
1424 manifest = repo_client.get_xml_manifest(
1392 """ 1425 """
1393<manifest> 1426<manifest>
1394 <remote name="default-remote" fetch="http://localhost" /> 1427 <remote name="default-remote" fetch="http://localhost" />
@@ -1400,21 +1433,24 @@ class ExtendProjectElementTests(ManifestParseTestCase):
1400</manifest> 1433</manifest>
1401""" 1434"""
1402 ) 1435 )
1403 self.assertEqual(list(manifest.projects[0].linkfiles)[0].src, "foo") 1436 assert list(manifest.projects[0].linkfiles)[0].src == "foo"
1404 self.assertEqual(list(manifest.projects[0].linkfiles)[0].dest, "bar") 1437 assert list(manifest.projects[0].linkfiles)[0].dest == "bar"
1405 self.assertEqual( 1438 assert (
1406 sort_attributes(manifest.ToXml().toxml()), 1439 sort_attributes(manifest.ToXml().toxml())
1407 '<?xml version="1.0" ?><manifest>' 1440 == '<?xml version="1.0" ?><manifest>'
1408 '<remote fetch="http://localhost" name="default-remote"/>' 1441 '<remote fetch="http://localhost" name="default-remote"/>'
1409 '<default remote="default-remote" revision="refs/heads/main"/>' 1442 '<default remote="default-remote" revision="refs/heads/main"/>'
1410 '<project name="myproject">' 1443 '<project name="myproject">'
1411 '<linkfile dest="bar" src="foo"/>' 1444 '<linkfile dest="bar" src="foo"/>'
1412 "</project>" 1445 "</project>"
1413 "</manifest>", 1446 "</manifest>"
1414 ) 1447 )
1415 1448
1416 def test_extend_project_duplicate_linkfiles(self): 1449 def test_extend_project_duplicate_linkfiles(
1417 root_m = self.manifest_dir / "root.xml" 1450 self, repo_client: RepoClient
1451 ) -> None:
1452 """Check duplicate linkfile handling in includes."""
1453 root_m = repo_client.manifest_dir / "root.xml"
1418 root_m.write_text( 1454 root_m.write_text(
1419 """ 1455 """
1420<manifest> 1456<manifest>
@@ -1426,21 +1462,21 @@ class ExtendProjectElementTests(ManifestParseTestCase):
1426</manifest> 1462</manifest>
1427""" 1463"""
1428 ) 1464 )
1429 (self.manifest_dir / "man1.xml").write_text( 1465 (repo_client.manifest_dir / "man1.xml").write_text(
1430 """ 1466 """
1431<manifest> 1467<manifest>
1432 <include name="common.xml" /> 1468 <include name="common.xml" />
1433</manifest> 1469</manifest>
1434""" 1470"""
1435 ) 1471 )
1436 (self.manifest_dir / "man2.xml").write_text( 1472 (repo_client.manifest_dir / "man2.xml").write_text(
1437 """ 1473 """
1438<manifest> 1474<manifest>
1439 <include name="common.xml" /> 1475 <include name="common.xml" />
1440</manifest> 1476</manifest>
1441""" 1477"""
1442 ) 1478 )
1443 (self.manifest_dir / "common.xml").write_text( 1479 (repo_client.manifest_dir / "common.xml").write_text(
1444 """ 1480 """
1445<manifest> 1481<manifest>
1446 <extend-project name="myproject"> 1482 <extend-project name="myproject">
@@ -1449,13 +1485,16 @@ class ExtendProjectElementTests(ManifestParseTestCase):
1449</manifest> 1485</manifest>
1450""" 1486"""
1451 ) 1487 )
1452 manifest = manifest_xml.XmlManifest(str(self.repodir), str(root_m)) 1488 manifest = manifest_xml.XmlManifest(
1453 self.assertEqual(len(manifest.projects[0].linkfiles), 1) 1489 str(repo_client.repodir), str(root_m)
1454 self.assertEqual(list(manifest.projects[0].linkfiles)[0].src, "foo") 1490 )
1455 self.assertEqual(list(manifest.projects[0].linkfiles)[0].dest, "bar") 1491 assert len(manifest.projects[0].linkfiles) == 1
1492 assert list(manifest.projects[0].linkfiles)[0].src == "foo"
1493 assert list(manifest.projects[0].linkfiles)[0].dest == "bar"
1456 1494
1457 def test_extend_project_annotations(self): 1495 def test_extend_project_annotations(self, repo_client: RepoClient) -> None:
1458 manifest = self.getXmlManifest( 1496 """Check annotation addition via extend-project."""
1497 manifest = repo_client.get_xml_manifest(
1459 """ 1498 """
1460<manifest> 1499<manifest>
1461 <remote name="default-remote" fetch="http://localhost" /> 1500 <remote name="default-remote" fetch="http://localhost" />
@@ -1467,21 +1506,24 @@ class ExtendProjectElementTests(ManifestParseTestCase):
1467</manifest> 1506</manifest>
1468""" 1507"""
1469 ) 1508 )
1470 self.assertEqual(manifest.projects[0].annotations[0].name, "foo") 1509 assert manifest.projects[0].annotations[0].name == "foo"
1471 self.assertEqual(manifest.projects[0].annotations[0].value, "bar") 1510 assert manifest.projects[0].annotations[0].value == "bar"
1472 self.assertEqual( 1511 assert (
1473 sort_attributes(manifest.ToXml().toxml()), 1512 sort_attributes(manifest.ToXml().toxml())
1474 '<?xml version="1.0" ?><manifest>' 1513 == '<?xml version="1.0" ?><manifest>'
1475 '<remote fetch="http://localhost" name="default-remote"/>' 1514 '<remote fetch="http://localhost" name="default-remote"/>'
1476 '<default remote="default-remote" revision="refs/heads/main"/>' 1515 '<default remote="default-remote" revision="refs/heads/main"/>'
1477 '<project name="myproject">' 1516 '<project name="myproject">'
1478 '<annotation name="foo" value="bar"/>' 1517 '<annotation name="foo" value="bar"/>'
1479 "</project>" 1518 "</project>"
1480 "</manifest>", 1519 "</manifest>"
1481 ) 1520 )
1482 1521
1483 def test_extend_project_annotations_multiples(self): 1522 def test_extend_project_annotations_multiples(
1484 manifest = self.getXmlManifest( 1523 self, repo_client: RepoClient
1524 ) -> None:
1525 """Check multiple annotation additions via extend-project."""
1526 manifest = repo_client.get_xml_manifest(
1485 """ 1527 """
1486<manifest> 1528<manifest>
1487 <remote name="default-remote" fetch="http://localhost" /> 1529 <remote name="default-remote" fetch="http://localhost" />
@@ -1497,18 +1539,17 @@ class ExtendProjectElementTests(ManifestParseTestCase):
1497</manifest> 1539</manifest>
1498""" 1540"""
1499 ) 1541 )
1500 self.assertEqual( 1542 assert [
1501 [(a.name, a.value) for a in manifest.projects[0].annotations], 1543 (a.name, a.value) for a in manifest.projects[0].annotations
1502 [ 1544 ] == [
1503 ("foo", "bar"), 1545 ("foo", "bar"),
1504 ("few", "bar"), 1546 ("few", "bar"),
1505 ("foo", "new_bar"), 1547 ("foo", "new_bar"),
1506 ("new", "anno"), 1548 ("new", "anno"),
1507 ], 1549 ]
1508 ) 1550 assert (
1509 self.assertEqual( 1551 sort_attributes(manifest.ToXml().toxml())
1510 sort_attributes(manifest.ToXml().toxml()), 1552 == '<?xml version="1.0" ?><manifest>'
1511 '<?xml version="1.0" ?><manifest>'
1512 '<remote fetch="http://localhost" name="default-remote"/>' 1553 '<remote fetch="http://localhost" name="default-remote"/>'
1513 '<default remote="default-remote" revision="refs/heads/main"/>' 1554 '<default remote="default-remote" revision="refs/heads/main"/>'
1514 '<project name="myproject">' 1555 '<project name="myproject">'
@@ -1517,81 +1558,78 @@ class ExtendProjectElementTests(ManifestParseTestCase):
1517 '<annotation name="foo" value="new_bar"/>' 1558 '<annotation name="foo" value="new_bar"/>'
1518 '<annotation name="new" value="anno"/>' 1559 '<annotation name="new" value="anno"/>'
1519 "</project>" 1560 "</project>"
1520 "</manifest>", 1561 "</manifest>"
1521 ) 1562 )
1522 1563
1523 1564
1524class NormalizeUrlTests(ManifestParseTestCase): 1565class TestNormalizeUrl:
1525 """Tests for normalize_url() in manifest_xml.py""" 1566 """Tests for normalize_url() in manifest_xml.py"""
1526 1567
1527 def test_has_trailing_slash(self): 1568 def test_has_trailing_slash(self) -> None:
1569 """Trailing slashes should be removed."""
1528 url = "http://foo.com/bar/baz/" 1570 url = "http://foo.com/bar/baz/"
1529 self.assertEqual( 1571 assert manifest_xml.normalize_url(url) == "http://foo.com/bar/baz"
1530 "http://foo.com/bar/baz", manifest_xml.normalize_url(url)
1531 )
1532 1572
1533 url = "http://foo.com/bar/" 1573 url = "http://foo.com/bar/"
1534 self.assertEqual("http://foo.com/bar", manifest_xml.normalize_url(url)) 1574 assert manifest_xml.normalize_url(url) == "http://foo.com/bar"
1535 1575
1536 def test_has_leading_slash(self): 1576 def test_has_leading_slash(self) -> None:
1537 """SCP-like syntax except a / comes before the : which git disallows.""" 1577 """SCP-like syntax except a / comes before the : which git disallows."""
1538 url = "/git@foo.com:bar/baf" 1578 url = "/git@foo.com:bar/baf"
1539 self.assertEqual(url, manifest_xml.normalize_url(url)) 1579 assert manifest_xml.normalize_url(url) == url
1540 1580
1541 url = "gi/t@foo.com:bar/baf" 1581 url = "gi/t@foo.com:bar/baf"
1542 self.assertEqual(url, manifest_xml.normalize_url(url)) 1582 assert manifest_xml.normalize_url(url) == url
1543 1583
1544 url = "git@fo/o.com:bar/baf" 1584 url = "git@fo/o.com:bar/baf"
1545 self.assertEqual(url, manifest_xml.normalize_url(url)) 1585 assert manifest_xml.normalize_url(url) == url
1546 1586
1547 def test_has_no_scheme(self): 1587 def test_has_no_scheme(self) -> None:
1548 """Deal with cases where we have no scheme, but we also 1588 """Deal with cases where we have no scheme, but we also
1549 aren't dealing with the git SCP-like syntax 1589 aren't dealing with the git SCP-like syntax
1550 """ 1590 """
1551 url = "foo.com/baf/bat" 1591 url = "foo.com/baf/bat"
1552 self.assertEqual(url, manifest_xml.normalize_url(url)) 1592 assert manifest_xml.normalize_url(url) == url
1553 1593
1554 url = "foo.com/baf" 1594 url = "foo.com/baf"
1555 self.assertEqual(url, manifest_xml.normalize_url(url)) 1595 assert manifest_xml.normalize_url(url) == url
1556 1596
1557 url = "git@foo.com/baf/bat" 1597 url = "git@foo.com/baf/bat"
1558 self.assertEqual(url, manifest_xml.normalize_url(url)) 1598 assert manifest_xml.normalize_url(url) == url
1559 1599
1560 url = "git@foo.com/baf" 1600 url = "git@foo.com/baf"
1561 self.assertEqual(url, manifest_xml.normalize_url(url)) 1601 assert manifest_xml.normalize_url(url) == url
1562 1602
1563 url = "/file/path/here" 1603 url = "/file/path/here"
1564 self.assertEqual(url, manifest_xml.normalize_url(url)) 1604 assert manifest_xml.normalize_url(url) == url
1565 1605
1566 def test_has_no_scheme_matches_scp_like_syntax(self): 1606 def test_has_no_scheme_matches_scp_like_syntax(self) -> None:
1607 """SCP-like syntax should be converted to ssh://."""
1567 url = "git@foo.com:bar/baf" 1608 url = "git@foo.com:bar/baf"
1568 self.assertEqual( 1609 assert manifest_xml.normalize_url(url) == "ssh://git@foo.com/bar/baf"
1569 "ssh://git@foo.com/bar/baf", manifest_xml.normalize_url(url)
1570 )
1571 1610
1572 url = "git@foo.com:bar/" 1611 url = "git@foo.com:bar/"
1573 self.assertEqual( 1612 assert manifest_xml.normalize_url(url) == "ssh://git@foo.com/bar"
1574 "ssh://git@foo.com/bar", manifest_xml.normalize_url(url)
1575 )
1576 1613
1577 def test_remote_url_resolution(self): 1614 def test_remote_url_resolution(self) -> None:
1615 """Check resolvedFetchUrl calculation."""
1578 remote = manifest_xml._XmlRemote( 1616 remote = manifest_xml._XmlRemote(
1579 name="foo", 1617 name="foo",
1580 fetch="git@github.com:org2/", 1618 fetch="git@github.com:org2/",
1581 manifestUrl="git@github.com:org2/custom_manifest.git", 1619 manifestUrl="git@github.com:org2/custom_manifest.git",
1582 ) 1620 )
1583 self.assertEqual("ssh://git@github.com/org2", remote.resolvedFetchUrl) 1621 assert remote.resolvedFetchUrl == "ssh://git@github.com/org2"
1584 1622
1585 remote = manifest_xml._XmlRemote( 1623 remote = manifest_xml._XmlRemote(
1586 name="foo", 1624 name="foo",
1587 fetch="ssh://git@github.com/org2/", 1625 fetch="ssh://git@github.com/org2/",
1588 manifestUrl="git@github.com:org2/custom_manifest.git", 1626 manifestUrl="git@github.com:org2/custom_manifest.git",
1589 ) 1627 )
1590 self.assertEqual("ssh://git@github.com/org2", remote.resolvedFetchUrl) 1628 assert remote.resolvedFetchUrl == "ssh://git@github.com/org2"
1591 1629
1592 remote = manifest_xml._XmlRemote( 1630 remote = manifest_xml._XmlRemote(
1593 name="foo", 1631 name="foo",
1594 fetch="git@github.com:org2/", 1632 fetch="git@github.com:org2/",
1595 manifestUrl="ssh://git@github.com/org2/custom_manifest.git", 1633 manifestUrl="ssh://git@github.com/org2/custom_manifest.git",
1596 ) 1634 )
1597 self.assertEqual("ssh://git@github.com/org2", remote.resolvedFetchUrl) 1635 assert remote.resolvedFetchUrl == "ssh://git@github.com/org2"
diff --git a/tests/test_subcmds_upload.py b/tests/test_subcmds_upload.py
index cd8889778..51c0a4cb7 100644
--- a/tests/test_subcmds_upload.py
+++ b/tests/test_subcmds_upload.py
@@ -14,9 +14,10 @@
14 14
15"""Unittests for the subcmds/upload.py module.""" 15"""Unittests for the subcmds/upload.py module."""
16 16
17import unittest
18from unittest import mock 17from unittest import mock
19 18
19import pytest
20
20from error import GitError 21from error import GitError
21from error import UploadError 22from error import UploadError
22from subcmds import upload 23from subcmds import upload
@@ -26,45 +27,39 @@ class UnexpectedError(Exception):
26 """An exception not expected by upload command.""" 27 """An exception not expected by upload command."""
27 28
28 29
29class UploadCommand(unittest.TestCase): 30# A stub people list (reviewers, cc).
30 """Check registered all_commands.""" 31_STUB_PEOPLE = ([], [])
31 32
32 def setUp(self): 33
33 self.cmd = upload.Upload() 34@pytest.fixture
34 self.branch = mock.MagicMock() 35def cmd() -> upload.Upload:
35 self.people = mock.MagicMock() 36 """Fixture to provide an Upload command instance with mocked methods."""
36 self.opt, _ = self.cmd.OptionParser.parse_args([]) 37 cmd = upload.Upload()
37 mock.patch.object( 38 with mock.patch.object(
38 self.cmd, "_AppendAutoList", return_value=None 39 cmd, "_AppendAutoList", return_value=None
39 ).start() 40 ), mock.patch.object(cmd, "git_event_log"):
40 mock.patch.object(self.cmd, "git_event_log").start() 41 yield cmd
41 42
42 def tearDown(self): 43
43 mock.patch.stopall() 44def test_UploadAndReport_UploadError(cmd: upload.Upload) -> None:
44 45 """Check UploadExitError raised when UploadError encountered."""
45 def test_UploadAndReport_UploadError(self): 46 opt, _ = cmd.OptionParser.parse_args([])
46 """Check UploadExitError raised when UploadError encountered.""" 47 with mock.patch.object(cmd, "_UploadBranch", side_effect=UploadError("")):
47 side_effect = UploadError("upload error") 48 with pytest.raises(upload.UploadExitError):
48 with mock.patch.object( 49 cmd._UploadAndReport(opt, [mock.MagicMock()], _STUB_PEOPLE)
49 self.cmd, "_UploadBranch", side_effect=side_effect 50
50 ): 51
51 with self.assertRaises(upload.UploadExitError): 52def test_UploadAndReport_GitError(cmd: upload.Upload) -> None:
52 self.cmd._UploadAndReport(self.opt, [self.branch], self.people) 53 """Check UploadExitError raised when GitError encountered."""
53 54 opt, _ = cmd.OptionParser.parse_args([])
54 def test_UploadAndReport_GitError(self): 55 with mock.patch.object(cmd, "_UploadBranch", side_effect=GitError("")):
55 """Check UploadExitError raised when GitError encountered.""" 56 with pytest.raises(upload.UploadExitError):
56 side_effect = GitError("some git error") 57 cmd._UploadAndReport(opt, [mock.MagicMock()], _STUB_PEOPLE)
57 with mock.patch.object( 58
58 self.cmd, "_UploadBranch", side_effect=side_effect 59
59 ): 60def test_UploadAndReport_UnhandledError(cmd: upload.Upload) -> None:
60 with self.assertRaises(upload.UploadExitError): 61 """Check UnexpectedError passed through."""
61 self.cmd._UploadAndReport(self.opt, [self.branch], self.people) 62 opt, _ = cmd.OptionParser.parse_args([])
62 63 with mock.patch.object(cmd, "_UploadBranch", side_effect=UnexpectedError):
63 def test_UploadAndReport_UnhandledError(self): 64 with pytest.raises(UnexpectedError):
64 """Check UnexpectedError passed through.""" 65 cmd._UploadAndReport(opt, [mock.MagicMock()], _STUB_PEOPLE)
65 side_effect = UnexpectedError("some os error")
66 with mock.patch.object(
67 self.cmd, "_UploadBranch", side_effect=side_effect
68 ):
69 with self.assertRaises(type(side_effect)):
70 self.cmd._UploadAndReport(self.opt, [self.branch], self.people)
diff --git a/tests/test_wrapper.py b/tests/test_wrapper.py
index 7845ee163..7a1a87289 100644
--- a/tests/test_wrapper.py
+++ b/tests/test_wrapper.py
@@ -19,261 +19,298 @@ import os
19import re 19import re
20import subprocess 20import subprocess
21import sys 21import sys
22import tempfile
23import unittest
24from unittest import mock 22from unittest import mock
25 23
24import pytest
26import utils_for_test 25import utils_for_test
27 26
28import main 27import main
29import wrapper 28import wrapper
30 29
31 30
32class RepoWrapperTestCase(unittest.TestCase): 31@pytest.fixture(autouse=True)
33 """TestCase for the wrapper module.""" 32def reset_wrapper() -> None:
33 """Reset the wrapper module every time."""
34 wrapper.Wrapper.cache_clear()
34 35
35 def setUp(self):
36 """Load the wrapper module every time."""
37 wrapper.Wrapper.cache_clear()
38 self.wrapper = wrapper.Wrapper()
39 36
37@pytest.fixture
38def repo_wrapper() -> wrapper.Wrapper:
39 """Fixture for the wrapper module."""
40 return wrapper.Wrapper()
40 41
41class RepoWrapperUnitTest(RepoWrapperTestCase): 42
43class GitCheckout:
44 """Class to hold git checkout info for tests."""
45
46 def __init__(self, git_dir, rev_list):
47 self.git_dir = git_dir
48 self.rev_list = rev_list
49
50
51@pytest.fixture(scope="module")
52def git_checkout(tmp_path_factory) -> GitCheckout:
53 """Fixture for tests that use a real/small git checkout.
54
55 Create a repo to operate on, but do it once per-test-run.
56 """
57 tempdir = tmp_path_factory.mktemp("repo-rev-tests")
58 run_git = wrapper.Wrapper().run_git
59
60 remote = os.path.join(tempdir, "remote")
61 os.mkdir(remote)
62
63 utils_for_test.init_git_tree(remote)
64 run_git("commit", "--allow-empty", "-minit", cwd=remote)
65 run_git("branch", "stable", cwd=remote)
66 run_git("tag", "v1.0", cwd=remote)
67 run_git("commit", "--allow-empty", "-m2nd commit", cwd=remote)
68 rev_list = run_git("rev-list", "HEAD", cwd=remote).stdout.splitlines()
69
70 run_git("init", cwd=tempdir)
71 run_git(
72 "fetch",
73 remote,
74 "+refs/heads/*:refs/remotes/origin/*",
75 cwd=tempdir,
76 )
77 yield GitCheckout(tempdir, rev_list)
78
79
80class TestRepoWrapper:
42 """Tests helper functions in the repo wrapper""" 81 """Tests helper functions in the repo wrapper"""
43 82
44 def test_version(self): 83 def test_version(self, repo_wrapper: wrapper.Wrapper) -> None:
45 """Make sure _Version works.""" 84 """Make sure _Version works."""
46 with self.assertRaises(SystemExit) as e: 85 with pytest.raises(SystemExit) as e:
47 with mock.patch("sys.stdout", new_callable=io.StringIO) as stdout: 86 with mock.patch("sys.stdout", new_callable=io.StringIO) as stdout:
48 with mock.patch( 87 with mock.patch(
49 "sys.stderr", new_callable=io.StringIO 88 "sys.stderr", new_callable=io.StringIO
50 ) as stderr: 89 ) as stderr:
51 self.wrapper._Version() 90 repo_wrapper._Version()
52 self.assertEqual(0, e.exception.code) 91 assert e.value.code == 0
53 self.assertEqual("", stderr.getvalue()) 92 assert stderr.getvalue() == ""
54 self.assertIn("repo launcher version", stdout.getvalue()) 93 assert "repo launcher version" in stdout.getvalue()
55 94
56 def test_python_constraints(self): 95 def test_python_constraints(self, repo_wrapper: wrapper.Wrapper) -> None:
57 """The launcher should never require newer than main.py.""" 96 """The launcher should never require newer than main.py."""
58 self.assertGreaterEqual( 97 assert (
59 main.MIN_PYTHON_VERSION_HARD, self.wrapper.MIN_PYTHON_VERSION_HARD 98 main.MIN_PYTHON_VERSION_HARD >= repo_wrapper.MIN_PYTHON_VERSION_HARD
60 ) 99 )
61 self.assertGreaterEqual( 100 assert (
62 main.MIN_PYTHON_VERSION_SOFT, self.wrapper.MIN_PYTHON_VERSION_SOFT 101 main.MIN_PYTHON_VERSION_SOFT >= repo_wrapper.MIN_PYTHON_VERSION_SOFT
63 ) 102 )
64 # Make sure the versions are themselves in sync. 103 # Make sure the versions are themselves in sync.
65 self.assertGreaterEqual( 104 assert (
66 self.wrapper.MIN_PYTHON_VERSION_SOFT, 105 repo_wrapper.MIN_PYTHON_VERSION_SOFT
67 self.wrapper.MIN_PYTHON_VERSION_HARD, 106 >= repo_wrapper.MIN_PYTHON_VERSION_HARD
68 ) 107 )
69 108
70 def test_init_parser(self): 109 def test_init_parser(self, repo_wrapper: wrapper.Wrapper) -> None:
71 """Make sure 'init' GetParser works.""" 110 """Make sure 'init' GetParser works."""
72 parser = self.wrapper.GetParser() 111 parser = repo_wrapper.GetParser()
73 opts, args = parser.parse_args([]) 112 opts, args = parser.parse_args([])
74 self.assertEqual([], args) 113 assert args == []
75 self.assertIsNone(opts.manifest_url) 114 assert opts.manifest_url is None
76 115
77 116
78class SetGitTrace2ParentSid(RepoWrapperTestCase): 117class TestSetGitTrace2ParentSid:
79 """Check SetGitTrace2ParentSid behavior.""" 118 """Check SetGitTrace2ParentSid behavior."""
80 119
81 KEY = "GIT_TRACE2_PARENT_SID" 120 KEY = "GIT_TRACE2_PARENT_SID"
82 VALID_FORMAT = re.compile(r"^repo-[0-9]{8}T[0-9]{6}Z-P[0-9a-f]{8}$") 121 VALID_FORMAT = re.compile(r"^repo-[0-9]{8}T[0-9]{6}Z-P[0-9a-f]{8}$")
83 122
84 def test_first_set(self): 123 def test_first_set(self, repo_wrapper: wrapper.Wrapper) -> None:
85 """Test env var not yet set.""" 124 """Test env var not yet set."""
86 env = {} 125 env = {}
87 self.wrapper.SetGitTrace2ParentSid(env) 126 repo_wrapper.SetGitTrace2ParentSid(env)
88 self.assertIn(self.KEY, env) 127 assert self.KEY in env
89 value = env[self.KEY] 128 value = env[self.KEY]
90 self.assertRegex(value, self.VALID_FORMAT) 129 assert self.VALID_FORMAT.match(value)
91 130
92 def test_append(self): 131 def test_append(self, repo_wrapper: wrapper.Wrapper) -> None:
93 """Test env var is appended.""" 132 """Test env var is appended."""
94 env = {self.KEY: "pfx"} 133 env = {self.KEY: "pfx"}
95 self.wrapper.SetGitTrace2ParentSid(env) 134 repo_wrapper.SetGitTrace2ParentSid(env)
96 self.assertIn(self.KEY, env) 135 assert self.KEY in env
97 value = env[self.KEY] 136 value = env[self.KEY]
98 self.assertTrue(value.startswith("pfx/")) 137 assert value.startswith("pfx/")
99 self.assertRegex(value[4:], self.VALID_FORMAT) 138 assert self.VALID_FORMAT.match(value[4:])
100 139
101 def test_global_context(self): 140 def test_global_context(self, repo_wrapper: wrapper.Wrapper) -> None:
102 """Check os.environ gets updated by default.""" 141 """Check os.environ gets updated by default."""
103 os.environ.pop(self.KEY, None) 142 os.environ.pop(self.KEY, None)
104 self.wrapper.SetGitTrace2ParentSid() 143 repo_wrapper.SetGitTrace2ParentSid()
105 self.assertIn(self.KEY, os.environ) 144 assert self.KEY in os.environ
106 value = os.environ[self.KEY] 145 value = os.environ[self.KEY]
107 self.assertRegex(value, self.VALID_FORMAT) 146 assert self.VALID_FORMAT.match(value)
108 147
109 148
110class RunCommand(RepoWrapperTestCase): 149class TestRunCommand:
111 """Check run_command behavior.""" 150 """Check run_command behavior."""
112 151
113 def test_capture(self): 152 def test_capture(self, repo_wrapper: wrapper.Wrapper) -> None:
114 """Check capture_output handling.""" 153 """Check capture_output handling."""
115 ret = self.wrapper.run_command(["echo", "hi"], capture_output=True) 154 ret = repo_wrapper.run_command(["echo", "hi"], capture_output=True)
116 # echo command appends OS specific linesep, but on Windows + Git Bash 155 # echo command appends OS specific linesep, but on Windows + Git Bash
117 # we get UNIX ending, so we allow both. 156 # we get UNIX ending, so we allow both.
118 self.assertIn(ret.stdout, ["hi" + os.linesep, "hi\n"]) 157 assert ret.stdout in ["hi" + os.linesep, "hi\n"]
119 158
120 def test_check(self): 159 def test_check(self, repo_wrapper: wrapper.Wrapper) -> None:
121 """Check check handling.""" 160 """Check check handling."""
122 self.wrapper.run_command(["true"], check=False) 161 repo_wrapper.run_command(["true"], check=False)
123 self.wrapper.run_command(["true"], check=True) 162 repo_wrapper.run_command(["true"], check=True)
124 self.wrapper.run_command(["false"], check=False) 163 repo_wrapper.run_command(["false"], check=False)
125 with self.assertRaises(subprocess.CalledProcessError): 164 with pytest.raises(subprocess.CalledProcessError):
126 self.wrapper.run_command(["false"], check=True) 165 repo_wrapper.run_command(["false"], check=True)
127 166
128 167
129class RunGit(RepoWrapperTestCase): 168class TestRunGit:
130 """Check run_git behavior.""" 169 """Check run_git behavior."""
131 170
132 def test_capture(self): 171 def test_capture(self, repo_wrapper: wrapper.Wrapper) -> None:
133 """Check capture_output handling.""" 172 """Check capture_output handling."""
134 ret = self.wrapper.run_git("--version") 173 ret = repo_wrapper.run_git("--version")
135 self.assertIn("git", ret.stdout) 174 assert "git" in ret.stdout
136 175
137 def test_check(self): 176 def test_check(self, repo_wrapper: wrapper.Wrapper) -> None:
138 """Check check handling.""" 177 """Check check handling."""
139 with self.assertRaises(self.wrapper.CloneFailure): 178 with pytest.raises(repo_wrapper.CloneFailure):
140 self.wrapper.run_git("--version-asdfasdf") 179 repo_wrapper.run_git("--version-asdfasdf")
141 self.wrapper.run_git("--version-asdfasdf", check=False) 180 repo_wrapper.run_git("--version-asdfasdf", check=False)
142 181
143 182
144class ParseGitVersion(RepoWrapperTestCase): 183class TestParseGitVersion:
145 """Check ParseGitVersion behavior.""" 184 """Check ParseGitVersion behavior."""
146 185
147 def test_autoload(self): 186 def test_autoload(self, repo_wrapper: wrapper.Wrapper) -> None:
148 """Check we can load the version from the live git.""" 187 """Check we can load the version from the live git."""
149 ret = self.wrapper.ParseGitVersion() 188 assert repo_wrapper.ParseGitVersion() is not None
150 self.assertIsNotNone(ret)
151 189
152 def test_bad_ver(self): 190 def test_bad_ver(self, repo_wrapper: wrapper.Wrapper) -> None:
153 """Check handling of bad git versions.""" 191 """Check handling of bad git versions."""
154 ret = self.wrapper.ParseGitVersion(ver_str="asdf") 192 assert repo_wrapper.ParseGitVersion(ver_str="asdf") is None
155 self.assertIsNone(ret)
156 193
157 def test_normal_ver(self): 194 def test_normal_ver(self, repo_wrapper: wrapper.Wrapper) -> None:
158 """Check handling of normal git versions.""" 195 """Check handling of normal git versions."""
159 ret = self.wrapper.ParseGitVersion(ver_str="git version 2.25.1") 196 ret = repo_wrapper.ParseGitVersion(ver_str="git version 2.25.1")
160 self.assertEqual(2, ret.major) 197 assert ret.major == 2
161 self.assertEqual(25, ret.minor) 198 assert ret.minor == 25
162 self.assertEqual(1, ret.micro) 199 assert ret.micro == 1
163 self.assertEqual("2.25.1", ret.full) 200 assert ret.full == "2.25.1"
164 201
165 def test_extended_ver(self): 202 def test_extended_ver(self, repo_wrapper: wrapper.Wrapper) -> None:
166 """Check handling of extended distro git versions.""" 203 """Check handling of extended distro git versions."""
167 ret = self.wrapper.ParseGitVersion( 204 ret = repo_wrapper.ParseGitVersion(
168 ver_str="git version 1.30.50.696.g5e7596f4ac-goog" 205 ver_str="git version 1.30.50.696.g5e7596f4ac-goog"
169 ) 206 )
170 self.assertEqual(1, ret.major) 207 assert ret.major == 1
171 self.assertEqual(30, ret.minor) 208 assert ret.minor == 30
172 self.assertEqual(50, ret.micro) 209 assert ret.micro == 50
173 self.assertEqual("1.30.50.696.g5e7596f4ac-goog", ret.full) 210 assert ret.full == "1.30.50.696.g5e7596f4ac-goog"
174 211
175 212
176class CheckGitVersion(RepoWrapperTestCase): 213class TestCheckGitVersion:
177 """Check _CheckGitVersion behavior.""" 214 """Check _CheckGitVersion behavior."""
178 215
179 def test_unknown(self): 216 def test_unknown(self, repo_wrapper: wrapper.Wrapper) -> None:
180 """Unknown versions should abort.""" 217 """Unknown versions should abort."""
181 with mock.patch.object( 218 with mock.patch.object(
182 self.wrapper, "ParseGitVersion", return_value=None 219 repo_wrapper, "ParseGitVersion", return_value=None
183 ): 220 ):
184 with self.assertRaises(self.wrapper.CloneFailure): 221 with pytest.raises(repo_wrapper.CloneFailure):
185 self.wrapper._CheckGitVersion() 222 repo_wrapper._CheckGitVersion()
186 223
187 def test_old(self): 224 def test_old(self, repo_wrapper: wrapper.Wrapper) -> None:
188 """Old versions should abort.""" 225 """Old versions should abort."""
189 with mock.patch.object( 226 with mock.patch.object(
190 self.wrapper, 227 repo_wrapper,
191 "ParseGitVersion", 228 "ParseGitVersion",
192 return_value=self.wrapper.GitVersion(1, 0, 0, "1.0.0"), 229 return_value=repo_wrapper.GitVersion(1, 0, 0, "1.0.0"),
193 ): 230 ):
194 with self.assertRaises(self.wrapper.CloneFailure): 231 with pytest.raises(repo_wrapper.CloneFailure):
195 self.wrapper._CheckGitVersion() 232 repo_wrapper._CheckGitVersion()
196 233
197 def test_new(self): 234 def test_new(self, repo_wrapper: wrapper.Wrapper) -> None:
198 """Newer versions should run fine.""" 235 """Newer versions should run fine."""
199 with mock.patch.object( 236 with mock.patch.object(
200 self.wrapper, 237 repo_wrapper,
201 "ParseGitVersion", 238 "ParseGitVersion",
202 return_value=self.wrapper.GitVersion(100, 0, 0, "100.0.0"), 239 return_value=repo_wrapper.GitVersion(100, 0, 0, "100.0.0"),
203 ): 240 ):
204 self.wrapper._CheckGitVersion() 241 repo_wrapper._CheckGitVersion()
205 242
206 243
207class Requirements(RepoWrapperTestCase): 244class TestRequirements:
208 """Check Requirements handling.""" 245 """Check Requirements handling."""
209 246
210 def test_missing_file(self): 247 def test_missing_file(self, repo_wrapper: wrapper.Wrapper) -> None:
211 """Don't crash if the file is missing (old version).""" 248 """Don't crash if the file is missing (old version)."""
212 self.assertIsNone( 249 assert (
213 self.wrapper.Requirements.from_dir(utils_for_test.THIS_DIR) 250 repo_wrapper.Requirements.from_dir(utils_for_test.THIS_DIR) is None
214 ) 251 )
215 self.assertIsNone( 252 assert (
216 self.wrapper.Requirements.from_file( 253 repo_wrapper.Requirements.from_file(
217 utils_for_test.THIS_DIR / "xxxxxxxxxxxxxxxxxxxxxxxx" 254 utils_for_test.THIS_DIR / "xxxxxxxxxxxxxxxxxxxxxxxx"
218 ) 255 )
256 is None
219 ) 257 )
220 258
221 def test_corrupt_data(self): 259 def test_corrupt_data(self, repo_wrapper: wrapper.Wrapper) -> None:
222 """If the file can't be parsed, don't blow up.""" 260 """If the file can't be parsed, don't blow up."""
223 self.assertIsNone(self.wrapper.Requirements.from_file(__file__)) 261 assert repo_wrapper.Requirements.from_file(__file__) is None
224 self.assertIsNone(self.wrapper.Requirements.from_data(b"x")) 262 assert repo_wrapper.Requirements.from_data(b"x") is None
225 263
226 def test_valid_data(self): 264 def test_valid_data(self, repo_wrapper: wrapper.Wrapper) -> None:
227 """Make sure we can parse the file we ship.""" 265 """Make sure we can parse the file we ship."""
228 self.assertIsNotNone(self.wrapper.Requirements.from_data(b"{}")) 266 assert repo_wrapper.Requirements.from_data(b"{}") is not None
229 rootdir = utils_for_test.THIS_DIR.parent 267 rootdir = utils_for_test.THIS_DIR.parent
230 self.assertIsNotNone(self.wrapper.Requirements.from_dir(rootdir)) 268 assert repo_wrapper.Requirements.from_dir(rootdir) is not None
231 self.assertIsNotNone( 269 assert (
232 self.wrapper.Requirements.from_file(rootdir / "requirements.json") 270 repo_wrapper.Requirements.from_file(rootdir / "requirements.json")
271 is not None
233 ) 272 )
234 273
235 def test_format_ver(self): 274 def test_format_ver(self, repo_wrapper: wrapper.Wrapper) -> None:
236 """Check format_ver can format.""" 275 """Check format_ver can format."""
237 self.assertEqual( 276 assert repo_wrapper.Requirements._format_ver((1, 2, 3)) == "1.2.3"
238 "1.2.3", self.wrapper.Requirements._format_ver((1, 2, 3)) 277 assert repo_wrapper.Requirements._format_ver([1]) == "1"
239 )
240 self.assertEqual("1", self.wrapper.Requirements._format_ver([1]))
241 278
242 def test_assert_all_unknown(self): 279 def test_assert_all_unknown(self, repo_wrapper: wrapper.Wrapper) -> None:
243 """Check assert_all works with incompatible file.""" 280 """Check assert_all works with incompatible file."""
244 reqs = self.wrapper.Requirements({}) 281 reqs = repo_wrapper.Requirements({})
245 reqs.assert_all() 282 reqs.assert_all()
246 283
247 def test_assert_all_new_repo(self): 284 def test_assert_all_new_repo(self, repo_wrapper: wrapper.Wrapper) -> None:
248 """Check assert_all accepts new enough repo.""" 285 """Check assert_all accepts new enough repo."""
249 reqs = self.wrapper.Requirements({"repo": {"hard": [1, 0]}}) 286 reqs = repo_wrapper.Requirements({"repo": {"hard": [1, 0]}})
250 reqs.assert_all() 287 reqs.assert_all()
251 288
252 def test_assert_all_old_repo(self): 289 def test_assert_all_old_repo(self, repo_wrapper: wrapper.Wrapper) -> None:
253 """Check assert_all rejects old repo.""" 290 """Check assert_all rejects old repo."""
254 reqs = self.wrapper.Requirements({"repo": {"hard": [99999, 0]}}) 291 reqs = repo_wrapper.Requirements({"repo": {"hard": [99999, 0]}})
255 with self.assertRaises(SystemExit): 292 with pytest.raises(SystemExit):
256 reqs.assert_all() 293 reqs.assert_all()
257 294
258 def test_assert_all_new_python(self): 295 def test_assert_all_new_python(self, repo_wrapper: wrapper.Wrapper) -> None:
259 """Check assert_all accepts new enough python.""" 296 """Check assert_all accepts new enough python."""
260 reqs = self.wrapper.Requirements({"python": {"hard": sys.version_info}}) 297 reqs = repo_wrapper.Requirements({"python": {"hard": sys.version_info}})
261 reqs.assert_all() 298 reqs.assert_all()
262 299
263 def test_assert_all_old_python(self): 300 def test_assert_all_old_python(self, repo_wrapper: wrapper.Wrapper) -> None:
264 """Check assert_all rejects old python.""" 301 """Check assert_all rejects old python."""
265 reqs = self.wrapper.Requirements({"python": {"hard": [99999, 0]}}) 302 reqs = repo_wrapper.Requirements({"python": {"hard": [99999, 0]}})
266 with self.assertRaises(SystemExit): 303 with pytest.raises(SystemExit):
267 reqs.assert_all() 304 reqs.assert_all()
268 305
269 def test_assert_ver_unknown(self): 306 def test_assert_ver_unknown(self, repo_wrapper: wrapper.Wrapper) -> None:
270 """Check assert_ver works with incompatible file.""" 307 """Check assert_ver works with incompatible file."""
271 reqs = self.wrapper.Requirements({}) 308 reqs = repo_wrapper.Requirements({})
272 reqs.assert_ver("xxx", (1, 0)) 309 reqs.assert_ver("xxx", (1, 0))
273 310
274 def test_assert_ver_new(self): 311 def test_assert_ver_new(self, repo_wrapper: wrapper.Wrapper) -> None:
275 """Check assert_ver allows new enough versions.""" 312 """Check assert_ver allows new enough versions."""
276 reqs = self.wrapper.Requirements( 313 reqs = repo_wrapper.Requirements(
277 {"git": {"hard": [1, 0], "soft": [2, 0]}} 314 {"git": {"hard": [1, 0], "soft": [2, 0]}}
278 ) 315 )
279 reqs.assert_ver("git", (1, 0)) 316 reqs.assert_ver("git", (1, 0))
@@ -281,274 +318,279 @@ class Requirements(RepoWrapperTestCase):
281 reqs.assert_ver("git", (2, 0)) 318 reqs.assert_ver("git", (2, 0))
282 reqs.assert_ver("git", (2, 5)) 319 reqs.assert_ver("git", (2, 5))
283 320
284 def test_assert_ver_old(self): 321 def test_assert_ver_old(self, repo_wrapper: wrapper.Wrapper) -> None:
285 """Check assert_ver rejects old versions.""" 322 """Check assert_ver rejects old versions."""
286 reqs = self.wrapper.Requirements( 323 reqs = repo_wrapper.Requirements(
287 {"git": {"hard": [1, 0], "soft": [2, 0]}} 324 {"git": {"hard": [1, 0], "soft": [2, 0]}}
288 ) 325 )
289 with self.assertRaises(SystemExit): 326 with pytest.raises(SystemExit):
290 reqs.assert_ver("git", (0, 5)) 327 reqs.assert_ver("git", (0, 5))
291 328
292 329
293class NeedSetupGnuPG(RepoWrapperTestCase): 330class TestNeedSetupGnuPG:
294 """Check NeedSetupGnuPG behavior.""" 331 """Check NeedSetupGnuPG behavior."""
295 332
296 def test_missing_dir(self): 333 def test_missing_dir(self, tmp_path, repo_wrapper: wrapper.Wrapper) -> None:
297 """The ~/.repoconfig tree doesn't exist yet.""" 334 """The ~/.repoconfig tree doesn't exist yet."""
298 with tempfile.TemporaryDirectory(prefix="repo-tests") as tempdir: 335 repo_wrapper.home_dot_repo = str(tmp_path / "foo")
299 self.wrapper.home_dot_repo = os.path.join(tempdir, "foo") 336 assert repo_wrapper.NeedSetupGnuPG()
300 self.assertTrue(self.wrapper.NeedSetupGnuPG())
301 337
302 def test_missing_keyring(self): 338 def test_missing_keyring(
339 self, tmp_path, repo_wrapper: wrapper.Wrapper
340 ) -> None:
303 """The keyring-version file doesn't exist yet.""" 341 """The keyring-version file doesn't exist yet."""
304 with tempfile.TemporaryDirectory(prefix="repo-tests") as tempdir: 342 repo_wrapper.home_dot_repo = str(tmp_path)
305 self.wrapper.home_dot_repo = tempdir 343 assert repo_wrapper.NeedSetupGnuPG()
306 self.assertTrue(self.wrapper.NeedSetupGnuPG())
307 344
308 def test_empty_keyring(self): 345 def test_empty_keyring(
346 self, tmp_path, repo_wrapper: wrapper.Wrapper
347 ) -> None:
309 """The keyring-version file exists, but is empty.""" 348 """The keyring-version file exists, but is empty."""
310 with tempfile.TemporaryDirectory(prefix="repo-tests") as tempdir: 349 repo_wrapper.home_dot_repo = str(tmp_path)
311 self.wrapper.home_dot_repo = tempdir 350 (tmp_path / "keyring-version").write_text("")
312 with open(os.path.join(tempdir, "keyring-version"), "w"): 351 assert repo_wrapper.NeedSetupGnuPG()
313 pass
314 self.assertTrue(self.wrapper.NeedSetupGnuPG())
315 352
316 def test_old_keyring(self): 353 def test_old_keyring(self, tmp_path, repo_wrapper: wrapper.Wrapper) -> None:
317 """The keyring-version file exists, but it's old.""" 354 """The keyring-version file exists, but it's old."""
318 with tempfile.TemporaryDirectory(prefix="repo-tests") as tempdir: 355 repo_wrapper.home_dot_repo = str(tmp_path)
319 self.wrapper.home_dot_repo = tempdir 356 (tmp_path / "keyring-version").write_text("1.0\n")
320 with open(os.path.join(tempdir, "keyring-version"), "w") as fp: 357 assert repo_wrapper.NeedSetupGnuPG()
321 fp.write("1.0\n")
322 self.assertTrue(self.wrapper.NeedSetupGnuPG())
323 358
324 def test_new_keyring(self): 359 def test_new_keyring(self, tmp_path, repo_wrapper: wrapper.Wrapper) -> None:
325 """The keyring-version file exists, and is up-to-date.""" 360 """The keyring-version file exists, and is up-to-date."""
326 with tempfile.TemporaryDirectory(prefix="repo-tests") as tempdir: 361 repo_wrapper.home_dot_repo = str(tmp_path)
327 self.wrapper.home_dot_repo = tempdir 362 (tmp_path / "keyring-version").write_text("1000.0\n")
328 with open(os.path.join(tempdir, "keyring-version"), "w") as fp: 363 assert not repo_wrapper.NeedSetupGnuPG()
329 fp.write("1000.0\n")
330 self.assertFalse(self.wrapper.NeedSetupGnuPG())
331 364
332 365
333class SetupGnuPG(RepoWrapperTestCase): 366class TestSetupGnuPG:
334 """Check SetupGnuPG behavior.""" 367 """Check SetupGnuPG behavior."""
335 368
336 def test_full(self): 369 def test_full(self, tmp_path, repo_wrapper: wrapper.Wrapper) -> None:
337 """Make sure it works completely.""" 370 """Make sure it works completely."""
338 with tempfile.TemporaryDirectory(prefix="repo-tests") as tempdir: 371 repo_wrapper.home_dot_repo = str(tmp_path)
339 self.wrapper.home_dot_repo = tempdir 372 repo_wrapper.gpg_dir = str(tmp_path / "gnupg")
340 self.wrapper.gpg_dir = os.path.join( 373 assert repo_wrapper.SetupGnuPG(True)
341 self.wrapper.home_dot_repo, "gnupg" 374 data = (tmp_path / "keyring-version").read_text()
342 ) 375 assert (
343 self.assertTrue(self.wrapper.SetupGnuPG(True)) 376 ".".join(str(x) for x in repo_wrapper.KEYRING_VERSION)
344 with open(os.path.join(tempdir, "keyring-version")) as fp: 377 == data.strip()
345 data = fp.read() 378 )
346 self.assertEqual(
347 ".".join(str(x) for x in self.wrapper.KEYRING_VERSION),
348 data.strip(),
349 )
350 379
351 380
352class VerifyRev(RepoWrapperTestCase): 381class TestVerifyRev:
353 """Check verify_rev behavior.""" 382 """Check verify_rev behavior."""
354 383
355 def test_verify_passes(self): 384 def test_verify_passes(self, repo_wrapper: wrapper.Wrapper) -> None:
356 """Check when we have a valid signed tag.""" 385 """Check when we have a valid signed tag."""
357 desc_result = subprocess.CompletedProcess([], 0, "v1.0\n", "") 386 desc_result = subprocess.CompletedProcess([], 0, "v1.0\n", "")
358 gpg_result = subprocess.CompletedProcess([], 0, "", "") 387 gpg_result = subprocess.CompletedProcess([], 0, "", "")
359 with mock.patch.object( 388 with mock.patch.object(
360 self.wrapper, "run_git", side_effect=(desc_result, gpg_result) 389 repo_wrapper, "run_git", side_effect=(desc_result, gpg_result)
361 ): 390 ):
362 ret = self.wrapper.verify_rev( 391 ret = repo_wrapper.verify_rev(
363 "/", "refs/heads/stable", "1234", True 392 "/", "refs/heads/stable", "1234", True
364 ) 393 )
365 self.assertEqual("v1.0^0", ret) 394 assert ret == "v1.0^0"
366 395
367 def test_unsigned_commit(self): 396 def test_unsigned_commit(self, repo_wrapper: wrapper.Wrapper) -> None:
368 """Check we fall back to signed tag when we have an unsigned commit.""" 397 """Check we fall back to signed tag when we have an unsigned commit."""
369 desc_result = subprocess.CompletedProcess([], 0, "v1.0-10-g1234\n", "") 398 desc_result = subprocess.CompletedProcess([], 0, "v1.0-10-g1234\n", "")
370 gpg_result = subprocess.CompletedProcess([], 0, "", "") 399 gpg_result = subprocess.CompletedProcess([], 0, "", "")
371 with mock.patch.object( 400 with mock.patch.object(
372 self.wrapper, "run_git", side_effect=(desc_result, gpg_result) 401 repo_wrapper, "run_git", side_effect=(desc_result, gpg_result)
373 ): 402 ):
374 ret = self.wrapper.verify_rev( 403 ret = repo_wrapper.verify_rev(
375 "/", "refs/heads/stable", "1234", True 404 "/", "refs/heads/stable", "1234", True
376 ) 405 )
377 self.assertEqual("v1.0^0", ret) 406 assert ret == "v1.0^0"
378 407
379 def test_verify_fails(self): 408 def test_verify_fails(self, repo_wrapper: wrapper.Wrapper) -> None:
380 """Check we fall back to signed tag when we have an unsigned commit.""" 409 """Check we fall back to signed tag when we have an unsigned commit."""
381 desc_result = subprocess.CompletedProcess([], 0, "v1.0-10-g1234\n", "") 410 desc_result = subprocess.CompletedProcess([], 0, "v1.0-10-g1234\n", "")
382 gpg_result = RuntimeError 411 gpg_result = RuntimeError
383 with mock.patch.object( 412 with mock.patch.object(
384 self.wrapper, "run_git", side_effect=(desc_result, gpg_result) 413 repo_wrapper, "run_git", side_effect=(desc_result, gpg_result)
385 ): 414 ):
386 with self.assertRaises(RuntimeError): 415 with pytest.raises(RuntimeError):
387 self.wrapper.verify_rev("/", "refs/heads/stable", "1234", True) 416 repo_wrapper.verify_rev("/", "refs/heads/stable", "1234", True)
388
389
390class GitCheckoutTestCase(RepoWrapperTestCase):
391 """Tests that use a real/small git checkout."""
392
393 GIT_DIR = None
394 REV_LIST = None
395
396 @classmethod
397 def setUpClass(cls):
398 # Create a repo to operate on, but do it once per-class.
399 cls.tempdirobj = tempfile.TemporaryDirectory(prefix="repo-rev-tests")
400 cls.GIT_DIR = cls.tempdirobj.name
401 run_git = wrapper.Wrapper().run_git
402
403 remote = os.path.join(cls.GIT_DIR, "remote")
404 os.mkdir(remote)
405
406 utils_for_test.init_git_tree(remote)
407 run_git("commit", "--allow-empty", "-minit", cwd=remote)
408 run_git("branch", "stable", cwd=remote)
409 run_git("tag", "v1.0", cwd=remote)
410 run_git("commit", "--allow-empty", "-m2nd commit", cwd=remote)
411 cls.REV_LIST = run_git(
412 "rev-list", "HEAD", cwd=remote
413 ).stdout.splitlines()
414
415 run_git("init", cwd=cls.GIT_DIR)
416 run_git(
417 "fetch",
418 remote,
419 "+refs/heads/*:refs/remotes/origin/*",
420 cwd=cls.GIT_DIR,
421 )
422
423 @classmethod
424 def tearDownClass(cls):
425 if not cls.tempdirobj:
426 return
427 417
428 cls.tempdirobj.cleanup()
429 418
430 419class TestResolveRepoRev:
431class ResolveRepoRev(GitCheckoutTestCase):
432 """Check resolve_repo_rev behavior.""" 420 """Check resolve_repo_rev behavior."""
433 421
434 def test_explicit_branch(self): 422 def test_explicit_branch(
423 self,
424 repo_wrapper: wrapper.Wrapper,
425 git_checkout: GitCheckout,
426 ) -> None:
435 """Check refs/heads/branch argument.""" 427 """Check refs/heads/branch argument."""
436 rrev, lrev = self.wrapper.resolve_repo_rev( 428 rrev, lrev = repo_wrapper.resolve_repo_rev(
437 self.GIT_DIR, "refs/heads/stable" 429 git_checkout.git_dir, "refs/heads/stable"
438 ) 430 )
439 self.assertEqual("refs/heads/stable", rrev) 431 assert rrev == "refs/heads/stable"
440 self.assertEqual(self.REV_LIST[1], lrev) 432 assert lrev == git_checkout.rev_list[1]
441 433
442 with self.assertRaises(self.wrapper.CloneFailure): 434 with pytest.raises(repo_wrapper.CloneFailure):
443 self.wrapper.resolve_repo_rev(self.GIT_DIR, "refs/heads/unknown") 435 repo_wrapper.resolve_repo_rev(
436 git_checkout.git_dir, "refs/heads/unknown"
437 )
444 438
445 def test_explicit_tag(self): 439 def test_explicit_tag(
440 self,
441 repo_wrapper: wrapper.Wrapper,
442 git_checkout: GitCheckout,
443 ) -> None:
446 """Check refs/tags/tag argument.""" 444 """Check refs/tags/tag argument."""
447 rrev, lrev = self.wrapper.resolve_repo_rev( 445 rrev, lrev = repo_wrapper.resolve_repo_rev(
448 self.GIT_DIR, "refs/tags/v1.0" 446 git_checkout.git_dir, "refs/tags/v1.0"
449 ) 447 )
450 self.assertEqual("refs/tags/v1.0", rrev) 448 assert rrev == "refs/tags/v1.0"
451 self.assertEqual(self.REV_LIST[1], lrev) 449 assert lrev == git_checkout.rev_list[1]
452 450
453 with self.assertRaises(self.wrapper.CloneFailure): 451 with pytest.raises(repo_wrapper.CloneFailure):
454 self.wrapper.resolve_repo_rev(self.GIT_DIR, "refs/tags/unknown") 452 repo_wrapper.resolve_repo_rev(
453 git_checkout.git_dir, "refs/tags/unknown"
454 )
455 455
456 def test_branch_name(self): 456 def test_branch_name(
457 self,
458 repo_wrapper: wrapper.Wrapper,
459 git_checkout: GitCheckout,
460 ) -> None:
457 """Check branch argument.""" 461 """Check branch argument."""
458 rrev, lrev = self.wrapper.resolve_repo_rev(self.GIT_DIR, "stable") 462 rrev, lrev = repo_wrapper.resolve_repo_rev(
459 self.assertEqual("refs/heads/stable", rrev) 463 git_checkout.git_dir, "stable"
460 self.assertEqual(self.REV_LIST[1], lrev) 464 )
461 465 assert rrev == "refs/heads/stable"
462 rrev, lrev = self.wrapper.resolve_repo_rev(self.GIT_DIR, "main") 466 assert lrev == git_checkout.rev_list[1]
463 self.assertEqual("refs/heads/main", rrev) 467
464 self.assertEqual(self.REV_LIST[0], lrev) 468 rrev, lrev = repo_wrapper.resolve_repo_rev(git_checkout.git_dir, "main")
465 469 assert rrev == "refs/heads/main"
466 def test_tag_name(self): 470 assert lrev == git_checkout.rev_list[0]
471
472 def test_tag_name(
473 self,
474 repo_wrapper: wrapper.Wrapper,
475 git_checkout: GitCheckout,
476 ) -> None:
467 """Check tag argument.""" 477 """Check tag argument."""
468 rrev, lrev = self.wrapper.resolve_repo_rev(self.GIT_DIR, "v1.0") 478 rrev, lrev = repo_wrapper.resolve_repo_rev(git_checkout.git_dir, "v1.0")
469 self.assertEqual("refs/tags/v1.0", rrev) 479 assert rrev == "refs/tags/v1.0"
470 self.assertEqual(self.REV_LIST[1], lrev) 480 assert lrev == git_checkout.rev_list[1]
471 481
472 def test_full_commit(self): 482 def test_full_commit(
483 self,
484 repo_wrapper: wrapper.Wrapper,
485 git_checkout: GitCheckout,
486 ) -> None:
473 """Check specific commit argument.""" 487 """Check specific commit argument."""
474 commit = self.REV_LIST[0] 488 commit = git_checkout.rev_list[0]
475 rrev, lrev = self.wrapper.resolve_repo_rev(self.GIT_DIR, commit) 489 rrev, lrev = repo_wrapper.resolve_repo_rev(git_checkout.git_dir, commit)
476 self.assertEqual(commit, rrev) 490 assert rrev == commit
477 self.assertEqual(commit, lrev) 491 assert lrev == commit
478 492
479 def test_partial_commit(self): 493 def test_partial_commit(
494 self,
495 repo_wrapper: wrapper.Wrapper,
496 git_checkout: GitCheckout,
497 ) -> None:
480 """Check specific (partial) commit argument.""" 498 """Check specific (partial) commit argument."""
481 commit = self.REV_LIST[0][0:20] 499 commit = git_checkout.rev_list[0][0:20]
482 rrev, lrev = self.wrapper.resolve_repo_rev(self.GIT_DIR, commit) 500 rrev, lrev = repo_wrapper.resolve_repo_rev(git_checkout.git_dir, commit)
483 self.assertEqual(self.REV_LIST[0], rrev) 501 assert rrev == git_checkout.rev_list[0]
484 self.assertEqual(self.REV_LIST[0], lrev) 502 assert lrev == git_checkout.rev_list[0]
485 503
486 def test_unknown(self): 504 def test_unknown(
505 self,
506 repo_wrapper: wrapper.Wrapper,
507 git_checkout: GitCheckout,
508 ) -> None:
487 """Check unknown ref/commit argument.""" 509 """Check unknown ref/commit argument."""
488 with self.assertRaises(self.wrapper.CloneFailure): 510 with pytest.raises(repo_wrapper.CloneFailure):
489 self.wrapper.resolve_repo_rev(self.GIT_DIR, "boooooooya") 511 repo_wrapper.resolve_repo_rev(git_checkout.git_dir, "boooooooya")
490 512
491 513
492class CheckRepoVerify(RepoWrapperTestCase): 514class TestCheckRepoVerify:
493 """Check check_repo_verify behavior.""" 515 """Check check_repo_verify behavior."""
494 516
495 def test_no_verify(self): 517 def test_no_verify(self, repo_wrapper: wrapper.Wrapper) -> None:
496 """Always fail with --no-repo-verify.""" 518 """Always fail with --no-repo-verify."""
497 self.assertFalse(self.wrapper.check_repo_verify(False)) 519 assert not repo_wrapper.check_repo_verify(False)
498 520
499 def test_gpg_initialized(self): 521 def test_gpg_initialized(
522 self,
523 repo_wrapper: wrapper.Wrapper,
524 ) -> None:
500 """Should pass if gpg is setup already.""" 525 """Should pass if gpg is setup already."""
501 with mock.patch.object( 526 with mock.patch.object(
502 self.wrapper, "NeedSetupGnuPG", return_value=False 527 repo_wrapper, "NeedSetupGnuPG", return_value=False
503 ): 528 ):
504 self.assertTrue(self.wrapper.check_repo_verify(True)) 529 assert repo_wrapper.check_repo_verify(True)
505 530
506 def test_need_gpg_setup(self): 531 def test_need_gpg_setup(
532 self,
533 repo_wrapper: wrapper.Wrapper,
534 ) -> None:
507 """Should pass/fail based on gpg setup.""" 535 """Should pass/fail based on gpg setup."""
508 with mock.patch.object( 536 with mock.patch.object(
509 self.wrapper, "NeedSetupGnuPG", return_value=True 537 repo_wrapper, "NeedSetupGnuPG", return_value=True
510 ): 538 ):
511 with mock.patch.object(self.wrapper, "SetupGnuPG") as m: 539 with mock.patch.object(repo_wrapper, "SetupGnuPG") as m:
512 m.return_value = True 540 m.return_value = True
513 self.assertTrue(self.wrapper.check_repo_verify(True)) 541 assert repo_wrapper.check_repo_verify(True)
514 542
515 m.return_value = False 543 m.return_value = False
516 self.assertFalse(self.wrapper.check_repo_verify(True)) 544 assert not repo_wrapper.check_repo_verify(True)
517 545
518 546
519class CheckRepoRev(GitCheckoutTestCase): 547class TestCheckRepoRev:
520 """Check check_repo_rev behavior.""" 548 """Check check_repo_rev behavior."""
521 549
522 def test_verify_works(self): 550 def test_verify_works(
551 self,
552 repo_wrapper: wrapper.Wrapper,
553 git_checkout: GitCheckout,
554 ) -> None:
523 """Should pass when verification passes.""" 555 """Should pass when verification passes."""
524 with mock.patch.object( 556 with mock.patch.object(
525 self.wrapper, "check_repo_verify", return_value=True 557 repo_wrapper, "check_repo_verify", return_value=True
526 ): 558 ):
527 with mock.patch.object( 559 with mock.patch.object(
528 self.wrapper, "verify_rev", return_value="12345" 560 repo_wrapper, "verify_rev", return_value="12345"
529 ): 561 ):
530 rrev, lrev = self.wrapper.check_repo_rev(self.GIT_DIR, "stable") 562 rrev, lrev = repo_wrapper.check_repo_rev(
531 self.assertEqual("refs/heads/stable", rrev) 563 git_checkout.git_dir, "stable"
532 self.assertEqual("12345", lrev) 564 )
533 565 assert rrev == "refs/heads/stable"
534 def test_verify_fails(self): 566 assert lrev == "12345"
567
568 def test_verify_fails(
569 self,
570 repo_wrapper: wrapper.Wrapper,
571 git_checkout: GitCheckout,
572 ) -> None:
535 """Should fail when verification fails.""" 573 """Should fail when verification fails."""
536 with mock.patch.object( 574 with mock.patch.object(
537 self.wrapper, "check_repo_verify", return_value=True 575 repo_wrapper, "check_repo_verify", return_value=True
538 ): 576 ):
539 with mock.patch.object( 577 with mock.patch.object(
540 self.wrapper, "verify_rev", side_effect=RuntimeError 578 repo_wrapper, "verify_rev", side_effect=RuntimeError
541 ): 579 ):
542 with self.assertRaises(RuntimeError): 580 with pytest.raises(RuntimeError):
543 self.wrapper.check_repo_rev(self.GIT_DIR, "stable") 581 repo_wrapper.check_repo_rev(git_checkout.git_dir, "stable")
544 582
545 def test_verify_ignore(self): 583 def test_verify_ignore(
584 self,
585 repo_wrapper: wrapper.Wrapper,
586 git_checkout: GitCheckout,
587 ) -> None:
546 """Should pass when verification is disabled.""" 588 """Should pass when verification is disabled."""
547 with mock.patch.object( 589 with mock.patch.object(
548 self.wrapper, "verify_rev", side_effect=RuntimeError 590 repo_wrapper, "verify_rev", side_effect=RuntimeError
549 ): 591 ):
550 rrev, lrev = self.wrapper.check_repo_rev( 592 rrev, lrev = repo_wrapper.check_repo_rev(
551 self.GIT_DIR, "stable", repo_verify=False 593 git_checkout.git_dir, "stable", repo_verify=False
552 ) 594 )
553 self.assertEqual("refs/heads/stable", rrev) 595 assert rrev == "refs/heads/stable"
554 self.assertEqual(self.REV_LIST[1], lrev) 596 assert lrev == git_checkout.rev_list[1]