Merge ~adam-collard/maas-ci/+git/system-tests:strict-mypy into ~maas-committers/maas-ci/+git/system-tests:master

Proposed by Adam Collard
Status: Merged
Approved by: Adam Collard
Approved revision: a91b80b938d188ca9c0eab2bd2b6d22b5b728540
Merge reported by: MAAS Lander
Merged at revision: not available
Proposed branch: ~adam-collard/maas-ci/+git/system-tests:strict-mypy
Merge into: ~maas-committers/maas-ci/+git/system-tests:master
Diff against target: 558 lines (+132/-78)
10 files modified
pyproject.toml (+1/-3)
systemtests/api.py (+94/-49)
systemtests/fixtures.py (+5/-5)
systemtests/lxd.py (+6/-5)
systemtests/region.py (+8/-4)
systemtests/subprocess.py (+4/-2)
systemtests/tests/conftest.py (+4/-4)
systemtests/tests/test_basic.py (+3/-1)
systemtests/tests/test_crud.py (+3/-3)
systemtests/utils.py (+4/-2)
Reviewer Review Type Date Requested Status
Alberto Donato (community) Approve
MAAS Lander Approve
Review via email: mp+408010@code.launchpad.net

Commit message

Add strict mypy validation.

To post a comment you must log in.
Revision history for this message
MAAS Lander (maas-lander) wrote :

UNIT TESTS
-b strict-mypy lp:~adam-collard/maas-ci/+git/system-tests into -b master lp:~maas-committers/maas-ci/+git/system-tests

STATUS: SUCCESS
COMMIT: a91b80b938d188ca9c0eab2bd2b6d22b5b728540

review: Approve
Revision history for this message
Alberto Donato (ack) wrote :

+1

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
diff --git a/pyproject.toml b/pyproject.toml
index 0c84d1d..cf33498 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -13,8 +13,6 @@ log_file_date_format = "%Y-%m-%d %H:%M:%S"
1313
1414
15[tool.mypy]15[tool.mypy]
16disallow_untyped_calls = true
17disallow_untyped_defs = true
18warn_unused_configs = true
19install_types = true16install_types = true
20non_interactive = true17non_interactive = true
18strict = true
diff --git a/systemtests/api.py b/systemtests/api.py
index 4aac548..a82fde4 100644
--- a/systemtests/api.py
+++ b/systemtests/api.py
@@ -29,10 +29,10 @@ class MAASAPIClient:
29 self.pull_file = partial(lxd.pull_file, maas_container)29 self.pull_file = partial(lxd.pull_file, maas_container)
30 self.push_file = partial(lxd.push_file, maas_container)30 self.push_file = partial(lxd.push_file, maas_container)
3131
32 def execute(self, cmd: list[str]) -> CompletedProcess:32 def execute(self, cmd: list[str]) -> CompletedProcess[str]:
33 return self.lxd.execute(self.maas_container, ["maas"] + cmd)33 return self.lxd.execute(self.maas_container, ["maas"] + cmd)
3434
35 def log_in(self, session: str, token: str) -> CompletedProcess:35 def log_in(self, session: str, token: str) -> CompletedProcess[str]:
36 return self.execute(["login", session, self.url, token])36 return self.execute(["login", session, self.url, token])
3737
3838
@@ -44,7 +44,7 @@ class AuthenticatedAPIClient:
44 def execute(44 def execute(
45 self,45 self,
46 cmd: list[str],46 cmd: list[str],
47 extra_params: dict[str, str] = None,47 extra_params: Optional[dict[str, str]] = None,
48 json_output: bool = True,48 json_output: bool = True,
49 ) -> Any:49 ) -> Any:
50 if extra_params:50 if extra_params:
@@ -52,31 +52,47 @@ class AuthenticatedAPIClient:
52 result = self.api_client.execute([self.session] + cmd)52 result = self.api_client.execute([self.session] + cmd)
53 return json.loads(result.stdout) if json_output else result.stdout53 return json.loads(result.stdout) if json_output else result.stdout
5454
55 def list_subnets(self) -> list[dict]:55 def list_subnets(self) -> list[dict[str, Any]]:
56 return self.execute(["subnets", "read"])56 subnets: list[dict[str, Any]] = self.execute(["subnets", "read"])
57 return subnets
5758
58 def create_subnet(self, name: str, cidr: str) -> dict:59 def create_subnet(self, name: str, cidr: str) -> dict[str, Any]:
59 return self.execute(["subnets", "create", "name=" + name, "cidr=" + cidr])60 subnet: dict[str, Any] = self.execute(
61 ["subnets", "create", "name=" + name, "cidr=" + cidr]
62 )
63 return subnet
6064
61 def delete_subnet(self, name: str) -> str:65 def delete_subnet(self, name: str) -> str:
62 return self.execute(["subnet", "delete", name], json_output=False)66 result: str = self.execute(["subnet", "delete", name], json_output=False)
67 return result
6368
64 def list_rack_controllers(self) -> list[dict]:69 def list_rack_controllers(self) -> list[dict[str, Any]]:
65 return self.execute(["rack-controllers", "read"])70 rack_controllers: list[dict[str, Any]] = self.execute(
71 ["rack-controllers", "read"]
72 )
73 return rack_controllers
6674
67 def list_ip_ranges(self) -> list[dict]:75 def list_ip_ranges(self) -> list[dict[str, Any]]:
68 return self.execute(["ipranges", "read"])76 ip_ranges: list[dict[str, Any]] = self.execute(["ipranges", "read"])
77 return ip_ranges
6978
70 def delete_ip_range(self, range_id: Union[str, int]) -> str:79 def delete_ip_range(self, range_id: Union[str, int]) -> str:
71 return self.execute(["iprange", "delete", str(range_id)], json_output=False)80 result: str = self.execute(
81 ["iprange", "delete", str(range_id)], json_output=False
82 )
83 return result
7284
73 def create_ip_range(self, start: str, end: str, range_type: str) -> dict:85 def create_ip_range(self, start: str, end: str, range_type: str) -> dict[str, Any]:
74 return self.execute(86 ip_range: dict[str, Any] = self.execute(
75 ["ipranges", "create", "type=dynamic", f"start_ip={start}", f"end_ip={end}"]87 ["ipranges", "create", "type=dynamic", f"start_ip={start}", f"end_ip={end}"]
76 )88 )
89 return ip_range
7790
78 def enable_dhcp(self, fabric: str, vlan: str, primary_rack: dict[str, str]) -> dict:91 def enable_dhcp(
79 return self.execute(92 self, fabric: str, vlan: str, primary_rack: dict[str, str]
93 ) -> dict[str, Any]:
94
95 vlan_obj: dict[str, Any] = self.execute(
80 [96 [
81 "vlan",97 "vlan",
82 "update",98 "update",
@@ -86,9 +102,13 @@ class AuthenticatedAPIClient:
86 f"primary_rack={primary_rack['system_id']}",102 f"primary_rack={primary_rack['system_id']}",
87 ]103 ]
88 )104 )
105 return vlan_obj
89106
90 def disable_dhcp(self, fabric: str, vlan: str) -> Any:107 def disable_dhcp(self, fabric: str, vlan: str) -> dict[str, Any]:
91 return self.execute(["vlan", "update", fabric, vlan, "dhcp_on=False"])108 vlan_obj: dict[str, Any] = self.execute(
109 ["vlan", "update", fabric, vlan, "dhcp_on=False"]
110 )
111 return vlan_obj
92112
93 def import_boot_resources(self) -> str:113 def import_boot_resources(self) -> str:
94 LOG.debug("Getting latest debug event for watermark")114 LOG.debug("Getting latest debug event for watermark")
@@ -124,46 +144,55 @@ class AuthenticatedAPIClient:
124 return result144 return result
125145
126 def is_importing_boot_resources(self) -> str:146 def is_importing_boot_resources(self) -> str:
127 return self.execute(["boot-resources", "is-importing"])147 result: str = self.execute(["boot-resources", "is-importing"])
148 return result
128149
129 def list_machines(self, **kwargs: str) -> list[dict[str, Any]]:150 def list_machines(self, **kwargs: str) -> list[dict[str, Any]]:
130 """151 """
131 machines read -h to know parameters available for kwargs152 machines read -h to know parameters available for kwargs
132 """153 """
133 cmd = ["machines", "read"]154 machines: list[dict[str, Any]] = self.execute(
134 return self.execute(cmd, extra_params=kwargs)155 ["machines", "read"], extra_params=kwargs
156 )
157 return machines
135158
136 def list_boot_images(self, rack_controller: dict[str, str]) -> dict[str, str]:159 def list_boot_images(self, rack_controller: dict[str, str]) -> dict[str, str]:
137 return self.execute(160 boot_images: dict[str, str] = self.execute(
138 ["rack-controller", "list-boot-images", rack_controller["system_id"]]161 ["rack-controller", "list-boot-images", rack_controller["system_id"]]
139 )162 )
163 return boot_images
140164
141 def import_boot_resources_in_rack(self, rack_controller: dict[str, str]) -> str:165 def import_boot_resources_in_rack(self, rack_controller: dict[str, str]) -> str:
142 return self.execute(166 result: str = self.execute(
143 ["rack-controller", "import-boot-images", rack_controller["system_id"]],167 ["rack-controller", "import-boot-images", rack_controller["system_id"]],
144 json_output=False,168 json_output=False,
145 )169 )
170 return result
146171
147 def commission_machine(self, machine: dict[str, str]) -> dict[str, str]:172 def commission_machine(self, machine: dict[str, str]) -> dict[str, str]:
148 result = self.execute(["machine", "commission", machine["system_id"]])173 result: dict[str, str] = self.execute(
174 ["machine", "commission", machine["system_id"]]
175 )
149 assert result["status_name"] == "Commissioning"176 assert result["status_name"] == "Commissioning"
150 return result177 return result
151178
152 def deploy_machine(self, machine: dict[str, str], **kwargs: str) -> dict[str, str]:179 def deploy_machine(self, machine: dict[str, str], **kwargs: str) -> dict[str, str]:
153 result = self.execute(180 result: dict[str, str] = self.execute(
154 ["machine", "deploy", machine["system_id"]], extra_params=kwargs181 ["machine", "deploy", machine["system_id"]], extra_params=kwargs
155 )182 )
156 assert result["status_name"] == "Deploying"183 assert result["status_name"] == "Deploying"
157 return result184 return result
158185
159 def create_ssh_key(self, public_key: str) -> None:186 def create_ssh_key(self, public_key: str) -> None:
160 result = self.execute(["sshkeys", "create", f"key={public_key}"])187 result: dict[str, str] = self.execute(
188 ["sshkeys", "create", f"key={public_key}"]
189 )
161 assert result["key"] == public_key190 assert result["key"] == public_key
162 return191 return
163192
164 def release_machine(self, machine: dict[str, str]) -> None:193 def release_machine(self, machine: dict[str, str]) -> None:
165 system_id: str = machine["system_id"]194 system_id: str = machine["system_id"]
166 result: dict = self.execute(["machine", "release", system_id])195 result: dict[str, Any] = self.execute(["machine", "release", system_id])
167 assert result["status_name"] == "Releasing"196 assert result["status_name"] == "Releasing"
168 wait_for_machines(197 wait_for_machines(
169 self,198 self,
@@ -238,11 +267,13 @@ class AuthenticatedAPIClient:
238 ["zones", "create", f"name={name}", f"description={description}"]267 ["zones", "create", f"name={name}", f"description={description}"]
239 )268 )
240269
241 def list_zones(self) -> list[dict]:270 def list_zones(self) -> list[dict[str, Any]]:
242 return self.execute(["zones", "read"])271 zones: list[dict[str, Any]] = self.execute(["zones", "read"])
272 return zones
243273
244 def read_zone(self, zone_name: str) -> Any:274 def read_zone(self, zone_name: str) -> Any:
245 return self.execute(["zone", "read", zone_name])275 zone: dict[str, Any] = self.execute(["zone", "read", zone_name])
276 return zone
246277
247 def update_zone(278 def update_zone(
248 self,279 self,
@@ -256,41 +287,49 @@ class AuthenticatedAPIClient:
256 if new_description is not None:287 if new_description is not None:
257 cmd.append(f"description={new_description}")288 cmd.append(f"description={new_description}")
258289
259 return self.execute(cmd)290 zone: dict[str, Any] = self.execute(cmd)
291 return zone
260292
261 def delete_zone(self, zone_name: str) -> str:293 def delete_zone(self, zone_name: str) -> str:
262 try:294 try:
263 return self.execute(["zone", "delete", zone_name], json_output=False)295 result: str = self.execute(["zone", "delete", zone_name], json_output=False)
264 except CalledProcessError as err:296 except CalledProcessError as err:
265 if "cannot be deleted" in err.stdout:297 if "cannot be deleted" in err.stdout:
266 raise CannotDeleteError(err.stdout)298 raise CannotDeleteError(err.stdout)
267 else:299 else:
268 raise300 raise
301 else:
302 return result
269303
270 def create_pool(self, name: str, description: str) -> Any:304 def create_pool(self, name: str, description: str) -> Any:
271 return self.execute(305 return self.execute(
272 ["resource-pools", "create", f"name={name}", f"description={description}"]306 ["resource-pools", "create", f"name={name}", f"description={description}"]
273 )307 )
274308
275 def list_pools(self) -> list[dict]:309 def list_pools(self) -> list[dict[str, Any]]:
276 return self.execute(["resource-pools", "read"])310 resource_pools: list[dict[str, Any]] = self.execute(["resource-pools", "read"])
311 return resource_pools
277312
278 def read_pool(self, pool: dict[str, Any]) -> dict:313 def read_pool(self, pool: dict[str, Any]) -> dict[str, Any]:
279 return self.execute(["resource-pool", "read", str(pool["id"])])314 resource_pool: dict[str, Any] = self.execute(
315 ["resource-pool", "read", str(pool["id"])]
316 )
317 return resource_pool
280318
281 def update_pool(319 def update_pool(
282 self,320 self,
283 pool: dict[str, Any],321 pool: dict[str, Any],
284 new_name: Optional[str] = None,322 new_name: Optional[str] = None,
285 new_description: Optional[str] = None,323 new_description: Optional[str] = None,
286 ) -> Any:324 ) -> dict[str, Any]:
287 cmd = ["resource-pool", "update", str(pool["id"])]325 cmd = ["resource-pool", "update", str(pool["id"])]
288 if new_name is not None:326 if new_name is not None:
289 cmd.append(f"name={new_name}")327 cmd.append(f"name={new_name}")
290 if new_description is not None:328 if new_description is not None:
291 cmd.append(f"description={new_description}")329 cmd.append(f"description={new_description}")
292330
293 return self.execute(cmd)331 pool_obj: dict[str, Any] = self.execute(cmd)
332 return pool_obj
294333
295 def delete_pool(self, pool: dict[str, Any]) -> Any:334 def delete_pool(self, pool: dict[str, Any]) -> Any:
296 try:335 try:
@@ -303,27 +342,31 @@ class AuthenticatedAPIClient:
303 else:342 else:
304 raise343 raise
305344
306 def create_space(self, name: str) -> Any:345 def create_space(self, name: str) -> dict[str, Any]:
307 return self.execute(["spaces", "create", f"name={name}"])346 space: dict[str, Any] = self.execute(["spaces", "create", f"name={name}"])
347 return space
308348
309 def list_spaces(self) -> list[dict]:349 def list_spaces(self) -> list[dict[str, Any]]:
310 return self.execute(["spaces", "read"])350 spaces: list[dict[str, Any]] = self.execute(["spaces", "read"])
351 return spaces
311352
312 def read_space(self, space: dict[str, Any]) -> Any:353 def read_space(self, space: dict[str, Any]) -> dict[str, Any]:
313 return self.execute(["space", "read", str(space["id"])])354 space_obj: dict[str, Any] = self.execute(["space", "read", str(space["id"])])
355 return space_obj
314356
315 def update_space(357 def update_space(
316 self, space: dict[str, Any], new_name: Optional[str] = None358 self, space: dict[str, Any], new_name: Optional[str] = None
317 ) -> Any:359 ) -> dict[str, Any]:
318 cmd = ["space", "update", str(space["id"])]360 cmd = ["space", "update", str(space["id"])]
319 if new_name is not None:361 if new_name is not None:
320 cmd.append(f"name={new_name}")362 cmd.append(f"name={new_name}")
321363
322 return self.execute(cmd)364 space_obj: dict[str, Any] = self.execute(cmd)
365 return space_obj
323366
324 def delete_space(self, space: dict[str, Any]) -> Any:367 def delete_space(self, space: dict[str, Any]) -> str:
325 try:368 try:
326 return self.execute(369 result: str = self.execute(
327 ["space", "delete", str(space["id"])], json_output=False370 ["space", "delete", str(space["id"])], json_output=False
328 )371 )
329 except CalledProcessError as err:372 except CalledProcessError as err:
@@ -331,3 +374,5 @@ class AuthenticatedAPIClient:
331 raise CannotDeleteError(err.stdout)374 raise CannotDeleteError(err.stdout)
332 else:375 else:
333 raise376 raise
377 else:
378 return result
diff --git a/systemtests/fixtures.py b/systemtests/fixtures.py
index fa50527..70278a7 100644
--- a/systemtests/fixtures.py
+++ b/systemtests/fixtures.py
@@ -8,7 +8,7 @@ import tempfile
8from logging import StreamHandler, getLogger8from logging import StreamHandler, getLogger
9from pathlib import Path9from pathlib import Path
10from textwrap import dedent10from textwrap import dedent
11from typing import IO, TYPE_CHECKING, Any, Iterator, Optional11from typing import TYPE_CHECKING, Any, Iterator, Optional, TextIO
1212
13import paramiko13import paramiko
14import pytest14import pytest
@@ -137,7 +137,7 @@ def maas_deb_repo(
137137
138138
139def get_user_data(139def get_user_data(
140 devices: dict[str, dict[str, Any]], cloud_config: Optional[dict] = None140 devices: dict[str, dict[str, Any]], cloud_config: Optional[dict[str, Any]] = None
141) -> str:141) -> str:
142 ethernets = {}142 ethernets = {}
143 for name, device in sorted(devices.items()):143 for name, device in sorted(devices.items()):
@@ -160,7 +160,7 @@ def get_user_data(
160 }160 }
161 )161 )
162162
163 user_data = "#cloud-config\n" + yaml.dump(cloud_config, default_style="|")163 user_data: str = "#cloud-config\n" + yaml.dump(cloud_config, default_style="|")
164 return user_data164 return user_data
165165
166166
@@ -325,12 +325,12 @@ def maas_api_client(maas_region: MAASRegion) -> Iterator[MAASAPIClient]:
325325
326326
327@pytest.fixture327@pytest.fixture
328def logstream() -> Iterator[IO]:328def logstream() -> Iterator[TextIO]:
329 yield io.StringIO()329 yield io.StringIO()
330330
331331
332@pytest.fixture(autouse=True)332@pytest.fixture(autouse=True)
333def testlog(logstream: IO) -> Iterator[Logger]:333def testlog(logstream: TextIO) -> Iterator[Logger]:
334 current_test = os.environ.get("PYTEST_CURRENT_TEST")334 current_test = os.environ.get("PYTEST_CURRENT_TEST")
335 logger = getLogger(f"systemtests.{current_test}")335 logger = getLogger(f"systemtests.{current_test}")
336 handler = StreamHandler(logstream)336 handler = StreamHandler(logstream)
diff --git a/systemtests/lxd.py b/systemtests/lxd.py
index a783f95..2c49809 100644
--- a/systemtests/lxd.py
+++ b/systemtests/lxd.py
@@ -35,7 +35,7 @@ class CLILXD:
35 except subprocess.CalledProcessError:35 except subprocess.CalledProcessError:
36 return False36 return False
3737
38 def _run(self, cmd: Union[str, list[str]]) -> subprocess.CompletedProcess:38 def _run(self, cmd: Union[str, list[str]]) -> subprocess.CompletedProcess[str]:
39 return run_with_logging(cmd, self.logger)39 return run_with_logging(cmd, self.logger)
4040
41 def create_container(41 def create_container(
@@ -89,7 +89,7 @@ class CLILXD:
8989
90 def pull_file(90 def pull_file(
91 self, container: str, file_path: str, local_path: str91 self, container: str, file_path: str, local_path: str
92 ) -> subprocess.CompletedProcess:92 ) -> subprocess.CompletedProcess[str]:
93 return self._run(93 return self._run(
94 [94 [
95 "lxc",95 "lxc",
@@ -122,9 +122,9 @@ class CLILXD:
122 def execute(122 def execute(
123 self,123 self,
124 container: str,124 container: str,
125 command: list,125 command: list[str],
126 environment: Optional[dict[str, str]] = None,126 environment: Optional[dict[str, str]] = None,
127 ) -> subprocess.CompletedProcess:127 ) -> subprocess.CompletedProcess[str]:
128 lxc_command = ["lxc", "exec", "--force-noninteractive", container]128 lxc_command = ["lxc", "exec", "--force-noninteractive", container]
129 if environment is not None:129 if environment is not None:
130 for key, value in environment.items():130 for key, value in environment.items():
@@ -146,7 +146,8 @@ class CLILXD:
146 for address in entry["state"]["network"]["eth0"]["addresses"]:146 for address in entry["state"]["network"]["eth0"]["addresses"]:
147 self.logger.info(f"Considering address: {address}")147 self.logger.info(f"Considering address: {address}")
148 if address["family"] == "inet":148 if address["family"] == "inet":
149 return address["address"]149 ip: str = address["address"]
150 return ip
150 else:151 else:
151 raise RuntimeError("Couldn't find an IP address")152 raise RuntimeError("Couldn't find an IP address")
152153
diff --git a/systemtests/region.py b/systemtests/region.py
index 4e96f81..5ca3901 100644
--- a/systemtests/region.py
+++ b/systemtests/region.py
@@ -21,7 +21,7 @@ class MAASRegion:
21 self.maas_container = maas_container21 self.maas_container = maas_container
22 self.installed_from_snap = installed_from_snap22 self.installed_from_snap = installed_from_snap
2323
24 def execute(self, command: list[str]) -> subprocess.CompletedProcess:24 def execute(self, command: list[str]) -> subprocess.CompletedProcess[str]:
25 lxd = get_lxd(LOG)25 lxd = get_lxd(LOG)
26 return lxd.execute(self.maas_container, command)26 return lxd.execute(self.maas_container, command)
2727
@@ -61,7 +61,9 @@ class MAASRegion:
61 "enable_http_proxy": enable_http_proxy,61 "enable_http_proxy": enable_http_proxy,
62 }62 }
6363
64 def enable_dhcp(self, config: dict, client: api.AuthenticatedAPIClient) -> None:64 def enable_dhcp(
65 self, config: dict[str, Any], client: api.AuthenticatedAPIClient
66 ) -> None:
65 rack_controllers = get_rack_controllers(client)67 rack_controllers = get_rack_controllers(client)
66 for network in config["networks"].values():68 for network in config["networks"].values():
67 primary_controller, link = get_dhcp_controller(69 primary_controller, link = get_dhcp_controller(
@@ -134,7 +136,9 @@ class MAASRegion:
134 return self.set_config("debug", "True")136 return self.set_config("debug", "True")
135137
136138
137def get_dhcp_controller(rack_controllers: list[dict], cidr: str) -> tuple[dict, dict]:139def get_dhcp_controller(
140 rack_controllers: list[dict[str, Any]], cidr: str
141) -> tuple[dict[str, Any], dict[str, Any]]:
138 for rack_controller in rack_controllers:142 for rack_controller in rack_controllers:
139 for interface in rack_controller["interface_set"]:143 for interface in rack_controller["interface_set"]:
140 for link in interface["links"]:144 for link in interface["links"]:
@@ -143,7 +147,7 @@ def get_dhcp_controller(rack_controllers: list[dict], cidr: str) -> tuple[dict,
143 raise AssertionError(f"Couldn't find rack controller managing DHCP for {cidr}")147 raise AssertionError(f"Couldn't find rack controller managing DHCP for {cidr}")
144148
145149
146def get_rack_controllers(client: api.AuthenticatedAPIClient) -> list[dict]:150def get_rack_controllers(client: api.AuthenticatedAPIClient) -> list[dict[str, Any]]:
147 """Repeatedly attempt to get rack controllers"""151 """Repeatedly attempt to get rack controllers"""
148 attempts = count(1)152 attempts = count(1)
149 for elapsed, _ in retries(timeout=300, delay=10):153 for elapsed, _ in retries(timeout=300, delay=10):
diff --git a/systemtests/subprocess.py b/systemtests/subprocess.py
index 2c34389..8e4219f 100644
--- a/systemtests/subprocess.py
+++ b/systemtests/subprocess.py
@@ -8,8 +8,10 @@ if TYPE_CHECKING:
88
99
10def run_with_logging(10def run_with_logging(
11 cmd: Union[str, list[str]], logger: logging.Logger, env: Optional[dict] = None11 cmd: Union[str, list[str]],
12) -> subprocess.CompletedProcess:12 logger: logging.Logger,
13 env: Optional[dict[str, str]] = None,
14) -> subprocess.CompletedProcess[str]:
13 logger.info("Running command: " + " ".join(cmd))15 logger.info("Running command: " + " ".join(cmd))
14 process = subprocess.Popen(16 process = subprocess.Popen(
15 cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, env=env17 cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, env=env
diff --git a/systemtests/tests/conftest.py b/systemtests/tests/conftest.py
index 7fa063d..7b8163a 100644
--- a/systemtests/tests/conftest.py
+++ b/systemtests/tests/conftest.py
@@ -1,7 +1,7 @@
1from __future__ import annotations1from __future__ import annotations
22
3import argparse3import argparse
4from typing import TYPE_CHECKING, Any, Iterator4from typing import TYPE_CHECKING, Any, Iterator, cast
55
6import pytest6import pytest
7import yaml7import yaml
@@ -52,14 +52,14 @@ def pytest_addoption(parser: Parser) -> None:
5252
5353
54@pytest.fixture(scope="session")54@pytest.fixture(scope="session")
55def config(request: pytest.FixtureRequest) -> dict:55def config(request: pytest.FixtureRequest) -> dict[str, Any]:
56 config_file = request.config.getoption("--ss-config")56 config_file = request.config.getoption("--ss-config")
57 if not config_file:57 if not config_file:
58 config_file = open("config.yaml", "r")58 config_file = open("config.yaml", "r")
59 return yaml.safe_load(config_file) if config_file else {}59 return cast(dict[str, Any], yaml.safe_load(config_file)) if config_file else {}
6060
6161
62@pytest.hookimpl(tryfirst=True, hookwrapper=True)62@pytest.hookimpl(tryfirst=True, hookwrapper=True) # type: ignore
63def pytest_runtest_makereport(item: Any, call: Any) -> Iterator[Any]:63def pytest_runtest_makereport(item: Any, call: Any) -> Iterator[Any]:
64 # execute all other hooks to obtain the report object64 # execute all other hooks to obtain the report object
65 outcome: _Result65 outcome: _Result
diff --git a/systemtests/tests/test_basic.py b/systemtests/tests/test_basic.py
index a94c69a..a653921 100644
--- a/systemtests/tests/test_basic.py
+++ b/systemtests/tests/test_basic.py
@@ -19,7 +19,9 @@ if TYPE_CHECKING:
1919
20class TestSetup:20class TestSetup:
21 @pytest.mark.skip_if_installed_from_snap("Prometheus is installed in the snap")21 @pytest.mark.skip_if_installed_from_snap("Prometheus is installed in the snap")
22 def test_setup_prometheus(self, maas_region: MAASRegion, config: dict) -> None:22 def test_setup_prometheus(
23 self, maas_region: MAASRegion, config: dict[str, str]
24 ) -> None:
23 result = maas_region.execute(25 result = maas_region.execute(
24 ["apt", "install", "python3-prometheus-client", "-y"]26 ["apt", "install", "python3-prometheus-client", "-y"]
25 )27 )
diff --git a/systemtests/tests/test_crud.py b/systemtests/tests/test_crud.py
index 366f815..0cb76f8 100644
--- a/systemtests/tests/test_crud.py
+++ b/systemtests/tests/test_crud.py
@@ -11,7 +11,7 @@ if TYPE_CHECKING:
11 from ..api import AuthenticatedAPIClient11 from ..api import AuthenticatedAPIClient
1212
1313
14@test_steps("create", "update", "delete")14@test_steps("create", "update", "delete") # type: ignore
15def test_zone(authenticated_admin: AuthenticatedAPIClient) -> Iterator[None]:15def test_zone(authenticated_admin: AuthenticatedAPIClient) -> Iterator[None]:
16 authenticated_admin.create_zone(16 authenticated_admin.create_zone(
17 name="test-zone", description="A zone created by system-tests"17 name="test-zone", description="A zone created by system-tests"
@@ -48,7 +48,7 @@ def test_zone(authenticated_admin: AuthenticatedAPIClient) -> Iterator[None]:
48 yield48 yield
4949
5050
51@test_steps("create", "update", "delete")51@test_steps("create", "update", "delete") # type: ignore
52def test_resource_pool(authenticated_admin: AuthenticatedAPIClient) -> Iterator[None]:52def test_resource_pool(authenticated_admin: AuthenticatedAPIClient) -> Iterator[None]:
53 authenticated_admin.create_pool(53 authenticated_admin.create_pool(
54 name="test-pool", description="A resource pool created by system-tests"54 name="test-pool", description="A resource pool created by system-tests"
@@ -87,7 +87,7 @@ def test_resource_pool(authenticated_admin: AuthenticatedAPIClient) -> Iterator[
87 yield87 yield
8888
8989
90@test_steps("create", "update", "delete")90@test_steps("create", "update", "delete") # type: ignore
91def test_spaces(authenticated_admin: AuthenticatedAPIClient) -> Iterator[None]:91def test_spaces(authenticated_admin: AuthenticatedAPIClient) -> Iterator[None]:
92 authenticated_admin.create_space(name="test-space")92 authenticated_admin.create_space(name="test-space")
93 spaces = authenticated_admin.list_spaces()93 spaces = authenticated_admin.list_spaces()
diff --git a/systemtests/utils.py b/systemtests/utils.py
index 68e17e9..7959492 100644
--- a/systemtests/utils.py
+++ b/systemtests/utils.py
@@ -3,7 +3,7 @@ from __future__ import annotations
3import random3import random
4import string4import string
5import time5import time
6from typing import TYPE_CHECKING, Any, Callable, Generator, Union6from typing import TYPE_CHECKING, Any, Callable, Iterator, Union
77
8import paramiko8import paramiko
9from retry.api import retry_call9from retry.api import retry_call
@@ -42,7 +42,9 @@ def randomstring(length: int = 10) -> str:
42 return "".join(random.choice(string.ascii_lowercase) for _ in range(length))42 return "".join(random.choice(string.ascii_lowercase) for _ in range(length))
4343
4444
45def retries(timeout: Union[int, float] = 30, delay: Union[int, float] = 1) -> Generator:45def retries(
46 timeout: Union[int, float] = 30, delay: Union[int, float] = 1
47) -> Iterator[tuple[float, float]]:
46 """Helper for retrying something, sleeping between attempts.48 """Helper for retrying something, sleeping between attempts.
4749
48 Yields ``(elapsed, remaining)`` tuples, giving times in seconds.50 Yields ``(elapsed, remaining)`` tuples, giving times in seconds.

Subscribers

People subscribed via source and target branches

to all changes: