Skip to content

Commit bc27016

Browse files
committed
šŸ› aiida_localhost: suffix label with xdist-worker id
Fixes the ``UNIQUE constraint failed: db_dbcomputer.label`` flake on ``tests-presto`` (#7347): the ``aiida_localhost`` fixture's Computer now lives under a label suffixed with the pytest-xdist worker id (``localhost-main`` outside xdist, ``localhost-gw0`` etc. inside), so it never collides with a literal ``'localhost'`` Computer that another code path has created in the same profile. Mechanism. The fixture's lookup filters on four fields (``label``, ``hostname``, ``scheduler_type``, ``transport_type``); the ``Computer.label`` UNIQUE constraint is single-column. When ``verdi presto`` or ``verdi devel launch-shell-job`` had already inserted a ``localhost`` Computer in the same profile, the four-field ``Computer.collection.get`` would miss it (mismatched fields), and the fallback ``store()`` would trip the single-column UNIQUE on label. Worker-suffixing the fixture's label removes the collision at the source — the fixture row simply lives under a different name from any literal-label row that other code paths may create. Note this also corrects a parallelism-correctness bug that predates the flake: the fixture was written before xdist was a concern and assumed exclusive use of "the" localhost row in the test profile. The literal ``'localhost'`` label remains the production contract for ``verdi presto`` and ``verdi devel launch-shell-job`` and is left untouched. Test usages that hardcoded ``orm.load_computer('localhost')`` to reach the fixture-created computer are switched to depend on ``aiida_localhost`` directly.
1 parent 6adc2b0 commit bc27016

3 files changed

Lines changed: 54 additions & 5 deletions

File tree

ā€Žsrc/aiida/tools/pytest_fixtures/orm.pyā€Ž

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
from __future__ import annotations
44

5+
import os
56
import pathlib
67
import typing as t
78

@@ -11,6 +12,20 @@
1112
from aiida.orm import Computer
1213

1314

15+
def _xdist_worker_id() -> str:
16+
"""Return the pytest-xdist worker id, or ``'main'`` when not running under xdist.
17+
18+
Used to suffix shared fixture labels (e.g. ``aiida_localhost``) so that simultaneous test
19+
sessions on the same machine, or independent test profiles within one session, cannot collide
20+
on database UNIQUE constraints. Read from the environment rather than depending on the
21+
``worker_id`` fixture so this works even when pytest-xdist is not loaded.
22+
23+
Note pytest-xdist's own ``worker_id`` fixture uses ``'master'`` for the non-xdist case; we
24+
diverge from that convention here because the value appears in user-visible labels.
25+
"""
26+
return os.environ.get('PYTEST_XDIST_WORKER', 'main')
27+
28+
1429
@pytest.fixture(scope='session')
1530
def ssh_key(tmp_path_factory) -> t.Generator[pathlib.Path, None, None]:
1631
"""Generate a temporary SSH key pair for the test session and return the filepath of the private key.
@@ -269,14 +284,20 @@ def if_exists_and_is_configured(label):
269284
def aiida_localhost(aiida_computer_local) -> 'Computer':
270285
"""Return a :class:`aiida.orm.computers.Computer` instance representing localhost with ``core.local`` transport.
271286
287+
The computer's label is suffixed with the pytest-xdist worker id (e.g. ``localhost-gw0``,
288+
or just ``localhost-main`` when not running under xdist). This keeps the fixture's row
289+
distinct from any literal ``'localhost'`` Computer that other tests or commands (notably
290+
``verdi presto``) may create in the same profile, avoiding UNIQUE-constraint collisions
291+
on ``Computer.label`` when fixture state and command state share a database.
292+
272293
Usage::
273294
274295
def test(aiida_localhost):
275296
assert aiida_localhost.transport_type == 'core.local'
276297
277298
:return: The computer.
278299
"""
279-
return aiida_computer_local(label='localhost')
300+
return aiida_computer_local(label=f'localhost-{_xdist_worker_id()}')
280301

281302

282303
@pytest.fixture

ā€Žtests/calculations/test_stash.pyā€Ž

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ def test_code_vs_stash_mode_conflict(stash_mode, fixture_sandbox, aiida_localhos
109109
code = orm.InstalledCode(
110110
label='dummy_code',
111111
default_calc_job_plugin='core.stash',
112-
computer=orm.load_computer(label='localhost'),
112+
computer=aiida_localhost,
113113
filepath_executable='/doesnot/exist/script.sh',
114114
)
115115

@@ -252,7 +252,7 @@ def test_submit_custom_code(fixture_sandbox, aiida_localhost, generate_calc_job,
252252
code = orm.InstalledCode(
253253
label='test_code',
254254
default_calc_job_plugin='core.stash',
255-
computer=orm.load_computer(label='localhost'),
255+
computer=aiida_localhost,
256256
filepath_executable=str(executable),
257257
)
258258
code.store()

ā€Žtests/tools/pytest_fixtures/test_orm.pyā€Ž

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,36 @@
1616

1717

1818
def test_aiida_localhost(aiida_localhost):
19-
"""Test the ``aiida_localhost`` fixture."""
20-
assert aiida_localhost.label == 'localhost'
19+
"""Test the ``aiida_localhost`` fixture.
20+
21+
The label is suffixed with the pytest-xdist worker id to avoid collisions with literal
22+
``'localhost'`` Computers created by other tests or commands (e.g. ``verdi presto``).
23+
"""
24+
assert aiida_localhost.label.startswith('localhost-')
25+
assert aiida_localhost.hostname == 'localhost'
26+
assert aiida_localhost.transport_type == 'core.local'
27+
assert aiida_localhost.scheduler_type == 'core.direct'
28+
29+
30+
@pytest.mark.usefixtures('aiida_profile_clean')
31+
def test_aiida_localhost_does_not_collide_with_literal_label(aiida_localhost):
32+
"""The fixture's Computer must coexist with a literal ``'localhost'`` Computer.
33+
34+
Other code paths (``verdi presto``, ``verdi devel launch-shell-job``) create a Computer
35+
with the literal ``'localhost'`` label in the same profile. Worker-suffixing the fixture's
36+
label makes its row distinct from those, so no UNIQUE-constraint collision is possible
37+
even when both rows live in the same profile.
38+
"""
39+
Computer(
40+
label='localhost',
41+
hostname='localhost',
42+
transport_type='core.ssh',
43+
scheduler_type='core.direct',
44+
workdir='/tmp',
45+
).store()
46+
47+
assert aiida_localhost.label != 'localhost'
48+
assert aiida_localhost.transport_type == 'core.local'
2149

2250

2351
@pytest.mark.parametrize(

0 commit comments

Comments
Ā (0)
⚔