Marco Ricci commited on 2024-12-08 17:44:25
Zeige 5 geänderte Dateien mit 55 Einfügungen und 31 Löschungen.
Though it does not do so by default, Python 3.9 can run with the old LL(1) parser instead of the PEG-based parser. Most importantly, the LL(1) parser does not support parenthesized context manager expressions, and this is why they have only become officially supported in Python 3.10, once the PEG-based parser became mandatory. Therefore, for compatibility, we replace every "proper" parenthesized context manager expression with equivalent explicit use of a `contextlib.ExitStack`, and every degenerate such expression by removing the parentheses. We now also explicitly test `derivepassphrase` with the LL(1) parser, for as long as Python 3.9 remains supported. While the use of a `contextlib.ExitStack` comes with additional runtime cost, by a lucky coincidence, all such proper usage of parenthesized context manager expressions is confined to the test suite, where we really don't care too much about slightly higher runtimes if it buys us more clarity, correctness and compatibility instead.
... | ... |
@@ -137,6 +137,13 @@ matrix-name-format = '{variable}_{value}' |
137 | 137 |
python = ["3.13", "3.12", "3.11", "3.10", "3.9", "pypy3.10", "pypy3.9"] |
138 | 138 |
cryptography = ["no", "yes"] |
139 | 139 |
hypothesis-profile = ["user-default"] |
140 |
+parser-version = ["PEG"] |
|
141 |
+ |
|
142 |
+[[tool.hatch.envs.hatch-test.matrix]] |
|
143 |
+python = ["3.9", "pypy3.9"] |
|
144 |
+cryptography = ["no", "yes"] |
|
145 |
+hypothesis-profile = ["user-default"] |
|
146 |
+parser-version = ["LL1"] |
|
140 | 147 |
|
141 | 148 |
[[tool.hatch.envs.hatch-test.matrix]] |
142 | 149 |
cryptography = ["yes"] |
... | ... |
@@ -149,6 +156,9 @@ matrix.cryptography.features = [ |
149 | 156 |
matrix.hypothesis-profile.env-vars = [ |
150 | 157 |
{ key = "HYPOTHESIS_PROFILE", if = ["ci", "default", "dev", "debug"] }, |
151 | 158 |
] |
159 |
+matrix.parser-version.env-vars = [ |
|
160 |
+ { key = "PYTHONOLDPARSER", value = "1", if = ["LL1"] }, |
|
161 |
+] |
|
152 | 162 |
|
153 | 163 |
[tool.hatch.envs.hatch-test.scripts] |
154 | 164 |
run = "pytest --hypothesis-profile={env:HYPOTHESIS_PROFILE:default}{env:HATCH_TEST_ARGS:} {args}" |
... | ... |
@@ -1413,11 +1413,14 @@ def isolated_config( |
1413 | 1413 |
) -> Iterator[None]: |
1414 | 1414 |
prog_name = cli.PROG_NAME |
1415 | 1415 |
env_name = prog_name.replace(' ', '_').upper() + '_PATH' |
1416 |
- with ( |
|
1417 |
- runner.isolated_filesystem(), |
|
1418 |
- cli.StandardCLILogging.ensure_standard_logging(), |
|
1419 |
- cli.StandardCLILogging.ensure_standard_warnings_logging(), |
|
1420 |
- ): |
|
1416 |
+ # Use parenthesized context manager expressions once Python 3.9 |
|
1417 |
+ # becomes unsupported. |
|
1418 |
+ with contextlib.ExitStack() as stack: |
|
1419 |
+ stack.enter_context(runner.isolated_filesystem()) |
|
1420 |
+ stack.enter_context(cli.StandardCLILogging.ensure_standard_logging()) |
|
1421 |
+ stack.enter_context( |
|
1422 |
+ cli.StandardCLILogging.ensure_standard_warnings_logging() |
|
1423 |
+ ) |
|
1421 | 1424 |
monkeypatch.setenv('HOME', os.getcwd()) |
1422 | 1425 |
monkeypatch.setenv('USERPROFILE', os.getcwd()) |
1423 | 1426 |
monkeypatch.delenv(env_name, raising=False) |
... | ... |
@@ -1476,10 +1479,13 @@ def isolated_vault_exporter_config( |
1476 | 1479 |
print(vault_config, file=outfile) |
1477 | 1480 |
elif isinstance(vault_config, bytes): |
1478 | 1481 |
os.makedirs('.vault', mode=0o700, exist_ok=True) |
1479 |
- with ( |
|
1480 |
- chdir('.vault'), |
|
1481 |
- tempfile.NamedTemporaryFile(suffix='.zip') as tmpzipfile, |
|
1482 |
- ): |
|
1482 |
+ # Use parenthesized context manager expressions here once |
|
1483 |
+ # Python 3.9 becomes unsupported. |
|
1484 |
+ with contextlib.ExitStack() as stack: |
|
1485 |
+ stack.enter_context(chdir('.vault')) |
|
1486 |
+ tmpzipfile = stack.enter_context( |
|
1487 |
+ tempfile.NamedTemporaryFile(suffix='.zip') |
|
1488 |
+ ) |
|
1483 | 1489 |
for line in vault_config.splitlines(): |
1484 | 1490 |
tmpzipfile.write(base64.standard_b64decode(line)) |
1485 | 1491 |
tmpzipfile.flush() |
... | ... |
@@ -1570,12 +1570,17 @@ class TestCLIUtils: |
1570 | 1570 |
self, monkeypatch: pytest.MonkeyPatch |
1571 | 1571 |
) -> None: |
1572 | 1572 |
runner = click.testing.CliRunner() |
1573 |
- with ( |
|
1573 |
+ # Use parenthesized context manager expressions here once Python |
|
1574 |
+ # 3.9 becomes unsupported. |
|
1575 |
+ with contextlib.ExitStack() as stack: |
|
1576 |
+ stack.enter_context( |
|
1574 | 1577 |
tests.isolated_vault_config( |
1575 | 1578 |
monkeypatch=monkeypatch, runner=runner, config={} |
1576 |
- ), |
|
1577 |
- pytest.raises(ValueError, match='Invalid vault config'), |
|
1578 |
- ): |
|
1579 |
+ ) |
|
1580 |
+ ) |
|
1581 |
+ stack.enter_context( |
|
1582 |
+ pytest.raises(ValueError, match='Invalid vault config') |
|
1583 |
+ ) |
|
1579 | 1584 |
cli._save_config(None) # type: ignore[arg-type] |
1580 | 1585 |
|
1581 | 1586 |
def test_111_prompt_for_selection_multiple(self) -> None: |
... | ... |
@@ -5,6 +5,7 @@ |
5 | 5 |
from __future__ import annotations |
6 | 6 |
|
7 | 7 |
import base64 |
8 |
+import contextlib |
|
8 | 9 |
import json |
9 | 10 |
from typing import TYPE_CHECKING |
10 | 11 |
|
... | ... |
@@ -265,12 +266,10 @@ class TestStoreroom: |
265 | 266 |
'signing_key': bytes(storeroom.KEY_SIZE), |
266 | 267 |
'hashing_key': bytes(storeroom.KEY_SIZE), |
267 | 268 |
} |
268 |
- with ( |
|
269 |
- tests.isolated_vault_exporter_config( |
|
269 |
+ with tests.isolated_vault_exporter_config( |
|
270 | 270 |
monkeypatch=monkeypatch, |
271 | 271 |
runner=runner, |
272 | 272 |
vault_config=tests.VAULT_STOREROOM_CONFIG_ZIPPED, |
273 |
- ), |
|
274 | 273 |
): |
275 | 274 |
with open('.vault/20', 'w', encoding='UTF-8') as outfile: |
276 | 275 |
print(config, file=outfile) |
... | ... |
@@ -292,13 +291,11 @@ class TestStoreroom: |
292 | 291 |
err_msg: str, |
293 | 292 |
) -> None: |
294 | 293 |
runner = click.testing.CliRunner(mix_stderr=False) |
295 |
- with ( |
|
296 |
- tests.isolated_vault_exporter_config( |
|
294 |
+ with tests.isolated_vault_exporter_config( |
|
297 | 295 |
monkeypatch=monkeypatch, |
298 | 296 |
runner=runner, |
299 | 297 |
vault_config=tests.VAULT_STOREROOM_CONFIG_ZIPPED, |
300 | 298 |
vault_key=tests.VAULT_MASTER_KEY, |
301 |
- ), |
|
302 | 299 |
): |
303 | 300 |
with open('.vault/.keys', 'w', encoding='UTF-8') as outfile: |
304 | 301 |
print(data, file=outfile) |
... | ... |
@@ -337,15 +334,18 @@ class TestStoreroom: |
337 | 334 |
error_text: str, |
338 | 335 |
) -> None: |
339 | 336 |
runner = click.testing.CliRunner(mix_stderr=False) |
340 |
- with ( |
|
337 |
+ # Use parenthesized context manager expressions once Python 3.9 |
|
338 |
+ # becomes unsupported. |
|
339 |
+ with contextlib.ExitStack() as stack: |
|
340 |
+ stack.enter_context( |
|
341 | 341 |
tests.isolated_vault_exporter_config( |
342 | 342 |
monkeypatch=monkeypatch, |
343 | 343 |
runner=runner, |
344 | 344 |
vault_config=zipped_config, |
345 | 345 |
vault_key=tests.VAULT_MASTER_KEY, |
346 |
- ), |
|
347 |
- pytest.raises(RuntimeError, match=error_text), |
|
348 |
- ): |
|
346 |
+ ) |
|
347 |
+ ) |
|
348 |
+ stack.enter_context(pytest.raises(RuntimeError, match=error_text)) |
|
349 | 349 |
storeroom.export_storeroom_data() |
350 | 350 |
|
351 | 351 |
def test_404_decrypt_keys_wrong_data_length(self) -> None: |
... | ... |
@@ -7,6 +7,7 @@ |
7 | 7 |
from __future__ import annotations |
8 | 8 |
|
9 | 9 |
import base64 |
10 |
+import contextlib |
|
10 | 11 |
import io |
11 | 12 |
import socket |
12 | 13 |
from typing import TYPE_CHECKING |
... | ... |
@@ -589,10 +590,11 @@ class TestAgentInteraction: |
589 | 590 |
) -> None: |
590 | 591 |
del running_ssh_agent |
591 | 592 |
|
592 |
- with ( |
|
593 |
- pytest.raises(exc_type, match=exc_pattern), |
|
594 |
- ssh_agent.SSHAgentClient() as client, |
|
595 |
- ): |
|
593 |
+ # Use parenthesized context manager expressions once Python 3.9 |
|
594 |
+ # becomes unsupported. |
|
595 |
+ with contextlib.ExitStack() as stack: |
|
596 |
+ stack.enter_context(pytest.raises(exc_type, match=exc_pattern)) |
|
597 |
+ client = stack.enter_context(ssh_agent.SSHAgentClient()) |
|
596 | 598 |
client.request(request_code, b'', response_code=response_code) |
597 | 599 |
|
598 | 600 |
@pytest.mark.parametrize( |
... | ... |
@@ -650,10 +652,11 @@ class TestAgentInteraction: |
650 | 652 |
assert single_code in response_codes |
651 | 653 |
return response_data # pragma: no cover |
652 | 654 |
|
653 |
- with ( |
|
654 |
- monkeypatch.context() as monkeypatch2, |
|
655 |
- ssh_agent.SSHAgentClient() as client, |
|
656 |
- ): |
|
655 |
+ # Use parenthesized context manager expressions once Python 3.9 |
|
656 |
+ # becomes unsupported. |
|
657 |
+ with contextlib.ExitStack() as stack: |
|
658 |
+ monkeypatch2 = stack.enter_context(monkeypatch.context()) |
|
659 |
+ client = stack.enter_context(ssh_agent.SSHAgentClient()) |
|
657 | 660 |
monkeypatch2.setattr(client, 'request', request) |
658 | 661 |
with pytest.raises( |
659 | 662 |
RuntimeError, match='Malformed response|does not match request' |
660 | 663 |