Marco Ricci commited on 2026-02-01 20:38:39
Zeige 4 geänderte Dateien mit 194 Einfügungen und 17 Löschungen.
Mark some tests as needing the ability to construct a second SSH agent client (on the same socket address) while another one is still connected. This works with most agents, except `gpg-agent` on The Annoying OS when masquerading as OpenSSH's `ssh-agent`. Presumably, `gpg-agent` handles the requests with a single thread, non-multiplexed, and so blocks all other agent clients from progressing. The symptoms are blocking during the connect call, then failing with "socket address not found" immediately once the other client closes its connection. We address the affected tests by monkeypatching the `ssh_agent.SSHAgentClient.ensure_agent_subcontext` context manager – the main way `derivepassphrase` internally interacts with the SSH agent – to return a singleton agent: the agent provided to the test function (via a fixture). We implement this as a machinery function to set up the environment explicitly, because this functionality hard to set up usefully as a test fixture: it interferes with testing SSH agent client constructor failures, and *those* are sometimes implemented as single `pytest.param`s of a common test function, but the test function cannot dynamically adapt the set of applicable fixtures to the specific parametrization. While specifically designed for use with `gpg-agent` on The Annoying OS, the interface is general in nature, and can be used with any declared SSH agent (except the fake agents). Since this internally works similarly to the "permitted SSH agents" feature of the test suite, we generalize the latter slightly to allow implementing the former.
| ... | ... |
@@ -37,6 +37,9 @@ def pytest_configure(config: pytest.Config) -> None: |
| 37 | 37 |
( |
| 38 | 38 |
"heavy_duty: " |
| 39 | 39 |
"mark test as a slow, heavy-duty test (e.g., an integration test)" |
| 40 |
+ "\n" |
|
| 41 |
+ "non_reentrant_agent: " |
|
| 42 |
+ "mark the agent (fixture output) as non-reentrant" |
|
| 40 | 43 |
), |
| 41 | 44 |
) |
| 42 | 45 |
hypothesis_machinery._hypothesis_settings_setup() |
| ... | ... |
@@ -390,6 +393,58 @@ The standard registry of agent spawning functions. |
| 390 | 393 |
""" |
| 391 | 394 |
|
| 392 | 395 |
|
| 396 |
+def external_agent_restriction( |
|
| 397 |
+ agent_type: data.KnownSSHAgent, |
|
| 398 |
+ env_var: str, |
|
| 399 |
+ *, |
|
| 400 |
+ default: bool = True, |
|
| 401 |
+) -> bool: # pragma: no cover [external] |
|
| 402 |
+ """Classify an SSH agent according to some environment variable. |
|
| 403 |
+ |
|
| 404 |
+ Look up the given SSH agent type in a certain environment variable, |
|
| 405 |
+ and report on whether the agent type is listed in that variable. |
|
| 406 |
+ This can be used to check if the agent is e.g. in a list of |
|
| 407 |
+ permitted agents, or if it is in the list of agents requiring |
|
| 408 |
+ special workarounds. Invalid agent type names are silently ignored. |
|
| 409 |
+ |
|
| 410 |
+ If the environment variable is empty, return a default value. |
|
| 411 |
+ |
|
| 412 |
+ By design, the stubbed agents have a fixed classification (the |
|
| 413 |
+ default value), unaffected by the environment variable. |
|
| 414 |
+ |
|
| 415 |
+ Args: |
|
| 416 |
+ agent_type: |
|
| 417 |
+ The agent type to classify. |
|
| 418 |
+ env_var: |
|
| 419 |
+ The environment variable in which to look up the agent type |
|
| 420 |
+ name. |
|
| 421 |
+ default: |
|
| 422 |
+ The value to return if the environment variable is unset, or |
|
| 423 |
+ if a stub agent is to be classified. |
|
| 424 |
+ |
|
| 425 |
+ Returns: |
|
| 426 |
+ If the agent type is not a stub agent, and the environment |
|
| 427 |
+ variable is non-empty, then `True` if the agent type is |
|
| 428 |
+ mentioned in the environment variable, and `False` otherwise; |
|
| 429 |
+ the default value otherwise. |
|
| 430 |
+ |
|
| 431 |
+ See also: |
|
| 432 |
+ [`is_agent_permitted`][] and [`is_agent_non_reentrant`][], which |
|
| 433 |
+ make use of this function internally. |
|
| 434 |
+ |
|
| 435 |
+ """ |
|
| 436 |
+ if agent_type == data.KnownSSHAgent.StubbedSSHAgent: |
|
| 437 |
+ return default |
|
| 438 |
+ if not os.environ.get(env_var): |
|
| 439 |
+ return default |
|
| 440 |
+ marked_agents = {
|
|
| 441 |
+ data.KnownSSHAgent(x) |
|
| 442 |
+ for x in os.environ[env_var].split(",")
|
|
| 443 |
+ if x in data.KnownSSHAgent.__members__ |
|
| 444 |
+ } |
|
| 445 |
+ return agent_type in marked_agents |
|
| 446 |
+ |
|
| 447 |
+ |
|
| 393 | 448 |
def is_agent_permitted( |
| 394 | 449 |
agent_type: data.KnownSSHAgent, |
| 395 | 450 |
) -> bool: # pragma: no cover [external] |
| ... | ... |
@@ -407,15 +462,35 @@ def is_agent_permitted( |
| 407 | 462 |
suite depends on their availability. |
| 408 | 463 |
|
| 409 | 464 |
""" |
| 410 |
- if not os.environ.get("PERMITTED_SSH_AGENTS"):
|
|
| 411 |
- return True |
|
| 412 |
- permitted_agents = {data.KnownSSHAgent.StubbedSSHAgent}
|
|
| 413 |
- permitted_agents.update({
|
|
| 414 |
- data.KnownSSHAgent(x) |
|
| 415 |
- for x in os.environ["PERMITTED_SSH_AGENTS"].split(",")
|
|
| 416 |
- if x in data.KnownSSHAgent.__members__ |
|
| 417 |
- }) |
|
| 418 |
- return agent_type in permitted_agents |
|
| 465 |
+ return external_agent_restriction( |
|
| 466 |
+ agent_type, "PERMITTED_SSH_AGENTS", default=True |
|
| 467 |
+ ) |
|
| 468 |
+ |
|
| 469 |
+ |
|
| 470 |
+def is_agent_non_reentrant( |
|
| 471 |
+ agent_type: data.KnownSSHAgent, |
|
| 472 |
+) -> bool: # pragma: no cover [external] |
|
| 473 |
+ """Is the given SSH agent assumed to be non-reentrant? |
|
| 474 |
+ |
|
| 475 |
+ If the environment variable `NON_REENTRANT_SSH_AGENTS` is given, it |
|
| 476 |
+ names a comma-separated list of known SSH agent names that are |
|
| 477 |
+ assumed to be non-reentrant, i.e., where the SSH agent cannot |
|
| 478 |
+ tolerate multiple clients connecting to it at the same time, and |
|
| 479 |
+ thus no client for the same agent may be constructed while another |
|
| 480 |
+ one is already connected. Invalid names are silently ignored. If |
|
| 481 |
+ not given, or empty, then all agents are considered reentrant, i.e., |
|
| 482 |
+ all agents are not considered non-reentrant. |
|
| 483 |
+ |
|
| 484 |
+ (To consider all agents non-reentrant, specify a single comma as the |
|
| 485 |
+ list. But see below.) |
|
| 486 |
+ |
|
| 487 |
+ The stubbed agents cannot be influenced in this manner, as the test |
|
| 488 |
+ suite depends on their reentrancy. |
|
| 489 |
+ |
|
| 490 |
+ """ |
|
| 491 |
+ return external_agent_restriction( |
|
| 492 |
+ agent_type, "NON_REENTRANT_SSH_AGENTS", default=False |
|
| 493 |
+ ) |
|
| 419 | 494 |
|
| 420 | 495 |
|
| 421 | 496 |
spawn_handlers_params: list[Sequence] = [] |
| ... | ... |
@@ -432,6 +507,13 @@ for key, handler in spawn_handlers.items(): |
| 432 | 507 |
"environment variable.", |
| 433 | 508 |
), |
| 434 | 509 |
] |
| 510 |
+ # Allow agents to be marked as non-reentrant. Workaround for an |
|
| 511 |
+ # issue with GnuPG 2.4.8 on The Annoying OS when `gpg-agent` |
|
| 512 |
+ # masquerades as OpenSSH's `ssh-agent`. |
|
| 513 |
+ if is_agent_non_reentrant( |
|
| 514 |
+ handler.agent_type |
|
| 515 |
+ ): # pragma: no cover [external] |
|
| 516 |
+ marks.append(pytest.mark.non_reentrant_agent) |
|
| 435 | 517 |
# Mark non-isolated agents as, well, using non-isolated agents. |
| 436 | 518 |
# Assume by default that an agent is potentially non-isolated unless |
| 437 | 519 |
# we can be absolutely sure it is isolated, by design. (The "stub" |
| ... | ... |
@@ -34,6 +34,7 @@ from typing_extensions import assert_never, overload |
| 34 | 34 |
|
| 35 | 35 |
import tests.data |
| 36 | 36 |
import tests.machinery |
| 37 |
+from derivepassphrase import _types, ssh_agent |
|
| 37 | 38 |
from derivepassphrase._internals import cli_helpers, cli_machinery |
| 38 | 39 |
from derivepassphrase.ssh_agent import socketprovider |
| 39 | 40 |
|
| ... | ... |
@@ -547,3 +548,56 @@ def isolated_vault_exporter_config( |
| 547 | 548 |
yield |
| 548 | 549 |
finally: |
| 549 | 550 |
cli_helpers.config_filename("write lock").unlink(missing_ok=True)
|
| 551 |
+ |
|
| 552 |
+ |
|
| 553 |
+def ensure_singleton_client_if_non_reentrant_agent( |
|
| 554 |
+ *, |
|
| 555 |
+ request: pytest.FixtureRequest, |
|
| 556 |
+ monkeypatch: pytest.MonkeyPatch, |
|
| 557 |
+ client: ssh_agent.SSHAgentClient, |
|
| 558 |
+) -> None: |
|
| 559 |
+ """Mangle SSH agent construction if the spawned agent is non-reentrant. |
|
| 560 |
+ |
|
| 561 |
+ Monkeypatch a suitable environment to ensure that constructing a new |
|
| 562 |
+ SSH agent client -- usually via the |
|
| 563 |
+ [`ssh_agent.SSHAgentClient.ensure_agent_subcontext`][] context |
|
| 564 |
+ manager -- doesn't really construct a new client, in case the agent |
|
| 565 |
+ is non-reentrant. |
|
| 566 |
+ |
|
| 567 |
+ Agent reentrancy is determined by the presence of the |
|
| 568 |
+ `non_reentrant_agent` mark. We change nothing if the agent is |
|
| 569 |
+ actually reentrant. |
|
| 570 |
+ |
|
| 571 |
+ Args: |
|
| 572 |
+ request: |
|
| 573 |
+ The `pytest` request fixture. Needed to get the active |
|
| 574 |
+ markers on this test function execution, which detail |
|
| 575 |
+ whether the agent is potentially non-reentrant. |
|
| 576 |
+ monkeypatch: |
|
| 577 |
+ A monkeypatching context to enrich. |
|
| 578 |
+ client: |
|
| 579 |
+ The singleton SSH agent client that is to be used. |
|
| 580 |
+ |
|
| 581 |
+ """ |
|
| 582 |
+ if ( |
|
| 583 |
+ request.node.get_closest_marker("non_reentrant_agent") is not None
|
|
| 584 |
+ ): # pragma: no cover [external] |
|
| 585 |
+ real_ensure_agent_subcontext = ( |
|
| 586 |
+ ssh_agent.SSHAgentClient.ensure_agent_subcontext |
|
| 587 |
+ ) |
|
| 588 |
+ |
|
| 589 |
+ def ensure_agent_subcontext( |
|
| 590 |
+ conn: ssh_agent.SSHAgentClient |
|
| 591 |
+ | _types.SSHAgentSocket |
|
| 592 |
+ | Sequence[str] |
|
| 593 |
+ | None = None, |
|
| 594 |
+ ) -> contextlib.AbstractContextManager[ssh_agent.SSHAgentClient]: |
|
| 595 |
+ if isinstance(conn, ssh_agent.SSHAgentClient): |
|
| 596 |
+ return real_ensure_agent_subcontext(conn) |
|
| 597 |
+ return real_ensure_agent_subcontext(client) |
|
| 598 |
+ |
|
| 599 |
+ monkeypatch.setattr( |
|
| 600 |
+ ssh_agent.SSHAgentClient, |
|
| 601 |
+ "ensure_agent_subcontext", |
|
| 602 |
+ ensure_agent_subcontext, |
|
| 603 |
+ ) |
| ... | ... |
@@ -29,7 +29,6 @@ import click |
| 29 | 29 |
import hypothesis |
| 30 | 30 |
import pytest |
| 31 | 31 |
from hypothesis import strategies |
| 32 |
-from typing_extensions import Any |
|
| 33 | 32 |
|
| 34 | 33 |
from derivepassphrase import _types, cli, ssh_agent, vault |
| 35 | 34 |
from derivepassphrase._internals import ( |
| ... | ... |
@@ -46,6 +45,8 @@ if TYPE_CHECKING: |
| 46 | 45 |
from collections.abc import Callable, Iterable, Iterator |
| 47 | 46 |
from typing import NoReturn |
| 48 | 47 |
|
| 48 |
+ from typing_extensions import Any |
|
| 49 |
+ |
|
| 49 | 50 |
|
| 50 | 51 |
DUMMY_SERVICE = data.DUMMY_SERVICE |
| 51 | 52 |
DUMMY_PASSPHRASE = data.DUMMY_PASSPHRASE |
| ... | ... |
@@ -480,6 +481,7 @@ class Parametrize(types.SimpleNamespace): |
| 480 | 481 |
"sign_action", |
| 481 | 482 |
"pattern", |
| 482 | 483 |
"warnings_patterns", |
| 484 |
+ "tests_construction_failure", |
|
| 483 | 485 |
], |
| 484 | 486 |
[ |
| 485 | 487 |
pytest.param( |
| ... | ... |
@@ -489,6 +491,7 @@ class Parametrize(types.SimpleNamespace): |
| 489 | 491 |
SignAction.FAIL, |
| 490 | 492 |
"not loaded into the agent", |
| 491 | 493 |
[], |
| 494 |
+ False, |
|
| 492 | 495 |
id="key-not-loaded", |
| 493 | 496 |
), |
| 494 | 497 |
pytest.param( |
| ... | ... |
@@ -498,6 +501,7 @@ class Parametrize(types.SimpleNamespace): |
| 498 | 501 |
SignAction.FAIL, |
| 499 | 502 |
"SSH agent failed to or refused to", |
| 500 | 503 |
[], |
| 504 |
+ False, |
|
| 501 | 505 |
id="list-keys-refused", |
| 502 | 506 |
), |
| 503 | 507 |
pytest.param( |
| ... | ... |
@@ -507,6 +511,7 @@ class Parametrize(types.SimpleNamespace): |
| 507 | 511 |
SignAction.FAIL, |
| 508 | 512 |
"SSH agent failed to or refused to", |
| 509 | 513 |
[], |
| 514 |
+ False, |
|
| 510 | 515 |
id="list-keys-protocol-error", |
| 511 | 516 |
), |
| 512 | 517 |
pytest.param( |
| ... | ... |
@@ -516,6 +521,7 @@ class Parametrize(types.SimpleNamespace): |
| 516 | 521 |
SignAction.FAIL, |
| 517 | 522 |
"Cannot connect to the SSH agent", |
| 518 | 523 |
[], |
| 524 |
+ True, |
|
| 519 | 525 |
id="agent-address-mangled", |
| 520 | 526 |
), |
| 521 | 527 |
pytest.param( |
| ... | ... |
@@ -525,6 +531,7 @@ class Parametrize(types.SimpleNamespace): |
| 525 | 531 |
SignAction.FAIL, |
| 526 | 532 |
"Cannot find any running SSH agent", |
| 527 | 533 |
[], |
| 534 |
+ True, |
|
| 528 | 535 |
id="agent-address-missing", |
| 529 | 536 |
), |
| 530 | 537 |
pytest.param( |
| ... | ... |
@@ -534,6 +541,7 @@ class Parametrize(types.SimpleNamespace): |
| 534 | 541 |
SignAction.FAIL, |
| 535 | 542 |
"does not support communicating with it", |
| 536 | 543 |
[], |
| 544 |
+ True, |
|
| 537 | 545 |
id="no-native-agent-available", |
| 538 | 546 |
), |
| 539 | 547 |
pytest.param( |
| ... | ... |
@@ -543,6 +551,7 @@ class Parametrize(types.SimpleNamespace): |
| 543 | 551 |
SignAction.FAIL, |
| 544 | 552 |
"does not support communicating with it", |
| 545 | 553 |
[], |
| 554 |
+ True, |
|
| 546 | 555 |
id="no-agents-in-agent-provider-list", |
| 547 | 556 |
), |
| 548 | 557 |
pytest.param( |
| ... | ... |
@@ -552,6 +561,7 @@ class Parametrize(types.SimpleNamespace): |
| 552 | 561 |
SignAction.FAIL, |
| 553 | 562 |
"does not support communicating with it", |
| 554 | 563 |
["Cannot connect to an SSH agent via UNIX domain sockets"], |
| 564 |
+ True, |
|
| 555 | 565 |
id="no-unix-domain-sockets", |
| 556 | 566 |
), |
| 557 | 567 |
pytest.param( |
| ... | ... |
@@ -561,6 +571,7 @@ class Parametrize(types.SimpleNamespace): |
| 561 | 571 |
SignAction.FAIL, |
| 562 | 572 |
"does not support communicating with it", |
| 563 | 573 |
["Cannot connect to an SSH agent via Windows named pipes"], |
| 574 |
+ True, |
|
| 564 | 575 |
id="no-windows-named-pipes", |
| 565 | 576 |
), |
| 566 | 577 |
pytest.param( |
| ... | ... |
@@ -570,6 +581,7 @@ class Parametrize(types.SimpleNamespace): |
| 570 | 581 |
SignAction.FAIL_RUNTIME, |
| 571 | 582 |
"violates the communication protocol", |
| 572 | 583 |
[], |
| 584 |
+ False, |
|
| 573 | 585 |
id="sign-violates-protocol", |
| 574 | 586 |
), |
| 575 | 587 |
], |
| ... | ... |
@@ -1466,6 +1478,7 @@ class TestMisc: |
| 1466 | 1478 |
@Parametrize.KEY_TO_PHRASE_SETTINGS |
| 1467 | 1479 |
def test_key_to_phrase( |
| 1468 | 1480 |
self, |
| 1481 |
+ request: pytest.FixtureRequest, |
|
| 1469 | 1482 |
ssh_agent_client_with_test_keys_loaded: ssh_agent.SSHAgentClient, |
| 1470 | 1483 |
list_keys_action: ListKeysAction | None, |
| 1471 | 1484 |
system_support_action: SystemSupportAction | None, |
| ... | ... |
@@ -1473,6 +1486,7 @@ class TestMisc: |
| 1473 | 1486 |
sign_action: SignAction, |
| 1474 | 1487 |
pattern: str, |
| 1475 | 1488 |
warnings_patterns: list[str], |
| 1489 |
+ tests_construction_failure: bool, |
|
| 1476 | 1490 |
) -> None: |
| 1477 | 1491 |
"""All errors in [`cli_helpers.key_to_phrase`][] are handled.""" |
| 1478 | 1492 |
|
| ... | ... |
@@ -1505,6 +1520,14 @@ class TestMisc: |
| 1505 | 1520 |
address_action(monkeypatch) |
| 1506 | 1521 |
if system_support_action: |
| 1507 | 1522 |
system_support_action(monkeypatch) |
| 1523 |
+ |
|
| 1524 |
+ if not tests_construction_failure: |
|
| 1525 |
+ pytest_machinery.ensure_singleton_client_if_non_reentrant_agent( |
|
| 1526 |
+ request=request, |
|
| 1527 |
+ monkeypatch=monkeypatch, |
|
| 1528 |
+ client=ssh_agent_client_with_test_keys_loaded, |
|
| 1529 |
+ ) |
|
| 1530 |
+ |
|
| 1508 | 1531 |
with pytest.raises(ErrCallback, match=pattern) as excinfo: |
| 1509 | 1532 |
cli_helpers.key_to_phrase( |
| 1510 | 1533 |
loaded_key, error_callback=err, warning_callback=warn |
| ... | ... |
@@ -27,7 +27,6 @@ from typing import TYPE_CHECKING |
| 27 | 27 |
import hypothesis |
| 28 | 28 |
import pytest |
| 29 | 29 |
from hypothesis import strategies |
| 30 |
-from typing_extensions import Any |
|
| 31 | 30 |
|
| 32 | 31 |
from derivepassphrase import _types, cli, ssh_agent |
| 33 | 32 |
from derivepassphrase._internals import ( |
| ... | ... |
@@ -43,6 +42,8 @@ if TYPE_CHECKING: |
| 43 | 42 |
from collections.abc import Iterator |
| 44 | 43 |
from typing import NoReturn |
| 45 | 44 |
|
| 45 |
+ from typing_extensions import Any |
|
| 46 |
+ |
|
| 46 | 47 |
DUMMY_SERVICE = data.DUMMY_SERVICE |
| 47 | 48 |
|
| 48 | 49 |
DUMMY_KEY1_B64 = data.DUMMY_KEY1_B64 |
| ... | ... |
@@ -879,15 +880,21 @@ class TestStoringConfigurationFailures: |
| 879 | 880 |
pass |
| 880 | 881 |
|
| 881 | 882 |
def test_fail_because_ssh_agent_has_no_keys_loaded( |
| 882 |
- self, spawn_ssh_agent: data.SpawnedSSHAgentInfo |
|
| 883 |
+ self, |
|
| 884 |
+ request: pytest.FixtureRequest, |
|
| 885 |
+ spawn_ssh_agent: data.SpawnedSSHAgentInfo, |
|
| 883 | 886 |
) -> None: |
| 884 | 887 |
"""Not holding any SSH keys during `--config --key` fails.""" |
| 885 |
- del spawn_ssh_agent |
|
| 886 | 888 |
with self._test( |
| 887 | 889 |
["--key"], |
| 888 | 890 |
error_text="no keys suitable", |
| 889 | 891 |
patch_suitable_ssh_keys=False, |
| 890 | 892 |
) as monkeypatch: |
| 893 |
+ pytest_machinery.ensure_singleton_client_if_non_reentrant_agent( |
|
| 894 |
+ request=request, |
|
| 895 |
+ monkeypatch=monkeypatch, |
|
| 896 |
+ client=spawn_ssh_agent.client, |
|
| 897 |
+ ) |
|
| 891 | 898 |
|
| 892 | 899 |
def func( |
| 893 | 900 |
*_args: Any, |
| ... | ... |
@@ -898,15 +905,21 @@ class TestStoringConfigurationFailures: |
| 898 | 905 |
monkeypatch.setattr(ssh_agent.SSHAgentClient, "list_keys", func) |
| 899 | 906 |
|
| 900 | 907 |
def test_store_config_fail_manual_ssh_agent_runtime_error( |
| 901 |
- self, spawn_ssh_agent: data.SpawnedSSHAgentInfo |
|
| 908 |
+ self, |
|
| 909 |
+ request: pytest.FixtureRequest, |
|
| 910 |
+ spawn_ssh_agent: data.SpawnedSSHAgentInfo, |
|
| 902 | 911 |
) -> None: |
| 903 | 912 |
"""Triggering an error in the SSH agent during `--config --key` leads to failure.""" |
| 904 |
- del spawn_ssh_agent |
|
| 905 | 913 |
with self._test( |
| 906 | 914 |
["--key"], |
| 907 | 915 |
error_text="violates the communication protocol", |
| 908 | 916 |
patch_suitable_ssh_keys=False, |
| 909 | 917 |
) as monkeypatch: |
| 918 |
+ pytest_machinery.ensure_singleton_client_if_non_reentrant_agent( |
|
| 919 |
+ request=request, |
|
| 920 |
+ monkeypatch=monkeypatch, |
|
| 921 |
+ client=spawn_ssh_agent.client, |
|
| 922 |
+ ) |
|
| 910 | 923 |
|
| 911 | 924 |
def raiser(*_args: Any, **_kwargs: Any) -> None: |
| 912 | 925 |
raise ssh_agent.TrailingDataError() |
| ... | ... |
@@ -914,13 +927,19 @@ class TestStoringConfigurationFailures: |
| 914 | 927 |
monkeypatch.setattr(ssh_agent.SSHAgentClient, "list_keys", raiser) |
| 915 | 928 |
|
| 916 | 929 |
def test_store_config_fail_manual_ssh_agent_refuses( |
| 917 |
- self, spawn_ssh_agent: data.SpawnedSSHAgentInfo |
|
| 930 |
+ self, |
|
| 931 |
+ request: pytest.FixtureRequest, |
|
| 932 |
+ spawn_ssh_agent: data.SpawnedSSHAgentInfo, |
|
| 918 | 933 |
) -> None: |
| 919 | 934 |
"""The SSH agent refusing during `--config --key` leads to failure.""" |
| 920 |
- del spawn_ssh_agent |
|
| 921 | 935 |
with self._test( |
| 922 | 936 |
["--key"], error_text="refused to", patch_suitable_ssh_keys=False |
| 923 | 937 |
) as monkeypatch: |
| 938 |
+ pytest_machinery.ensure_singleton_client_if_non_reentrant_agent( |
|
| 939 |
+ request=request, |
|
| 940 |
+ monkeypatch=monkeypatch, |
|
| 941 |
+ client=spawn_ssh_agent.client, |
|
| 942 |
+ ) |
|
| 924 | 943 |
|
| 925 | 944 |
def func(*_args: Any, **_kwargs: Any) -> NoReturn: |
| 926 | 945 |
raise ssh_agent.SSHAgentFailedError( |
| 927 | 946 |