Skip to content

Commit e8bbb4d

Browse files
authored
Merge pull request #525 from pytest-dev/524-add-global-session-uuid
Add global testrun uid
2 parents ec13cea + 069f959 commit e8bbb4d

File tree

9 files changed

+114
-7
lines changed

9 files changed

+114
-7
lines changed

README.rst

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,41 @@ defined:
276276
The information about the worker_id in a test is stored in the ``TestReport`` as
277277
well, under the ``worker_id`` attribute.
278278

279+
280+
Uniquely identifying the current test run
281+
-----------------------------------------
282+
283+
*New in version 1.32.*
284+
285+
If you need to globally distinguish one test run from others in your
286+
workers, you can use the ``testrun_uid`` fixture. For instance, let's say you
287+
wanted to create a separate database for each test run:
288+
289+
.. code-block:: python
290+
291+
import pytest
292+
from posix_ipc import Semaphore, O_CREAT
293+
294+
@pytest.fixture(scope="session", autouse=True)
295+
def create_unique_database(testrun_uid):
296+
""" create a unique database for this particular test run """
297+
database_url = f"psql://myapp-{testrun_uid}"
298+
299+
with Semaphore(f"/{testrun_uid}-lock", flags=O_CREAT, initial_value=1):
300+
if not database_exists(database_url):
301+
create_database(database_url)
302+
303+
@pytest.fixture()
304+
def db(testrun_uid):
305+
""" retrieve unique database """
306+
database_url = f"psql://myapp-{testrun_uid}"
307+
return database_get_instance(database_url)
308+
309+
310+
Additionally, during a test run, the following environment variable is defined:
311+
312+
* ``PYTEST_XDIST_TESTRUNUID``: the unique id of the test run.
313+
279314
Acessing ``sys.argv`` from the master node in workers
280315
-----------------------------------------------------
281316

changelog/524.feature

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Add `testrun_uid` fixture. This is a shared value that uniquely identifies a test run among all workers.
2+
This also adds a `PYTEST_XDIST_TESTRUNUID` environment variable that is accessible within a test as well as a command line option `--testrunuid` to manually set the value from outside.

src/xdist/plugin.py

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import os
2+
import uuid
23

34
import py
45
import pytest
@@ -122,12 +123,22 @@ def pytest_addoption(parser):
122123
metavar="GLOB",
123124
help="add expression for ignores when rsyncing to remote tx nodes.",
124125
)
125-
126126
group.addoption(
127127
"--boxed",
128128
action="store_true",
129129
help="backward compatibility alias for pytest-forked --forked",
130130
)
131+
group.addoption(
132+
"--testrunuid",
133+
action="store",
134+
help=(
135+
"provide an identifier shared amongst all workers as the value of "
136+
"the 'testrun_uid' fixture,\n\n,"
137+
"if not provided, 'testrun_uid' is filled with a new unique string "
138+
"on every test run."
139+
),
140+
)
141+
131142
parser.addini(
132143
"rsyncdirs",
133144
"list of (relative) paths to be rsynced for remote distributed testing.",
@@ -214,3 +225,12 @@ def worker_id(request):
214225
return request.config.workerinput["workerid"]
215226
else:
216227
return "master"
228+
229+
230+
@pytest.fixture(scope="session")
231+
def testrun_uid(request):
232+
"""Return the unique id of the current test."""
233+
if hasattr(request.config, "workerinput"):
234+
return request.config.workerinput["testrunuid"]
235+
else:
236+
return uuid.uuid4().hex

src/xdist/remote.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ class WorkerInteractor(object):
2222
def __init__(self, config, channel):
2323
self.config = config
2424
self.workerid = config.workerinput.get("workerid", "?")
25+
self.testrunuid = config.workerinput["testrunuid"]
2526
self.log = py.log.Producer("worker-%s" % self.workerid)
2627
if not config.option.debug:
2728
py.log.setconsumer(self.log._keywords, None)
@@ -112,6 +113,7 @@ def pytest_runtest_logreport(self, report):
112113
)
113114
data["item_index"] = self.item_index
114115
data["worker_id"] = self.workerid
116+
data["testrun_uid"] = self.testrunuid
115117
assert self.session.items[self.item_index].nodeid == report.nodeid
116118
self.sendevent("testreport", data=data)
117119

@@ -238,6 +240,7 @@ def setup_config(config, basetemp):
238240
importpath + os.pathsep + os.environ.get("PYTHONPATH", "")
239241
)
240242

243+
os.environ["PYTEST_XDIST_TESTRUNUID"] = workerinput["testrunuid"]
241244
os.environ["PYTEST_XDIST_WORKER"] = workerinput["workerid"]
242245
os.environ["PYTEST_XDIST_WORKER_COUNT"] = str(workerinput["workercount"])
243246

src/xdist/workermanage.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import os
44
import re
55
import sys
6+
import uuid
67

78
import py
89
import pytest
@@ -35,6 +36,9 @@ class NodeManager(object):
3536
def __init__(self, config, specs=None, defaultchdir="pyexecnetcache"):
3637
self.config = config
3738
self.trace = self.config.trace.get("nodemanager")
39+
self.testrunuid = self.config.getoption("testrunuid")
40+
if self.testrunuid is None:
41+
self.testrunuid = uuid.uuid4().hex
3842
self.group = execnet.Group()
3943
if specs is None:
4044
specs = self._getxspecs()
@@ -222,6 +226,7 @@ def __init__(self, nodemanager, gateway, config, putevent):
222226
"workercount": len(nodemanager.specs),
223227
"slaveid": gateway.id,
224228
"slavecount": len(nodemanager.specs),
229+
"testrunuid": nodemanager.testrunuid,
225230
"mainargv": sys.argv,
226231
}
227232
# TODO: deprecated name, backward compatibility only. Remove it in future

testing/acceptance_test.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1070,6 +1070,29 @@ def test_worker_id1(worker_id, run_num):
10701070
assert worker_ids == {"gw0", "gw1"}
10711071

10721072

1073+
@pytest.mark.parametrize("n", [0, 2])
1074+
def test_testrun_uid_fixture(testdir, n):
1075+
import glob
1076+
1077+
f = testdir.makepyfile(
1078+
"""
1079+
import pytest
1080+
@pytest.mark.parametrize("run_num", range(2))
1081+
def test_testrun_uid1(testrun_uid, run_num):
1082+
with open("testrun_uid%s.txt" % run_num, "w") as f:
1083+
f.write(testrun_uid)
1084+
"""
1085+
)
1086+
result = testdir.runpytest(f, "-n%d" % n)
1087+
result.stdout.fnmatch_lines("* 2 passed in *")
1088+
testrun_uids = set()
1089+
for fname in glob.glob(str(testdir.tmpdir.join("*.txt"))):
1090+
with open(fname) as f:
1091+
testrun_uids.add(f.read().strip())
1092+
assert len(testrun_uids) == 1
1093+
assert len(testrun_uids.pop()) == 32
1094+
1095+
10731096
@pytest.mark.parametrize("tb", ["auto", "long", "short", "no", "line", "native"])
10741097
def test_error_report_styles(testdir, tb):
10751098
testdir.makepyfile(

testing/test_newhooks.py

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,28 +15,32 @@ def test_c(): pass
1515

1616
def test_runtest_logreport(self, testdir):
1717
"""Test that log reports from pytest_runtest_logreport when running
18-
with xdist contain "node", "nodeid" and "worker_id" attributes. (#8)
18+
with xdist contain "node", "nodeid", "worker_id", and "testrun_uid" attributes. (#8)
1919
"""
2020
testdir.makeconftest(
2121
"""
2222
def pytest_runtest_logreport(report):
2323
if hasattr(report, 'node'):
2424
if report.when == "call":
2525
workerid = report.node.workerinput['workerid']
26+
testrunuid = report.node.workerinput['testrunuid']
2627
if workerid != report.worker_id:
2728
print("HOOK: Worker id mismatch: %s %s"
2829
% (workerid, report.worker_id))
30+
elif testrunuid != report.testrun_uid:
31+
print("HOOK: Testrun uid mismatch: %s %s"
32+
% (testrunuid, report.testrun_uid))
2933
else:
30-
print("HOOK: %s %s"
31-
% (report.nodeid, report.worker_id))
34+
print("HOOK: %s %s %s"
35+
% (report.nodeid, report.worker_id, report.testrun_uid))
3236
"""
3337
)
3438
res = testdir.runpytest("-n1", "-s")
3539
res.stdout.fnmatch_lines(
3640
[
37-
"*HOOK: test_runtest_logreport.py::test_a gw0*",
38-
"*HOOK: test_runtest_logreport.py::test_b gw0*",
39-
"*HOOK: test_runtest_logreport.py::test_c gw0*",
41+
"*HOOK: test_runtest_logreport.py::test_a gw0 *",
42+
"*HOOK: test_runtest_logreport.py::test_b gw0 *",
43+
"*HOOK: test_runtest_logreport.py::test_c gw0 *",
4044
"*3 passed*",
4145
]
4246
)

testing/test_plugin.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,18 @@ def test_dsession_with_collect_only(testdir):
9595
assert not config.pluginmanager.hasplugin("dsession")
9696

9797

98+
def test_testrunuid_provided(testdir):
99+
config = testdir.parseconfigure("--testrunuid", "test123", "--tx=popen")
100+
nm = NodeManager(config)
101+
assert nm.testrunuid == "test123"
102+
103+
104+
def test_testrunuid_generated(testdir):
105+
config = testdir.parseconfigure("--tx=popen")
106+
nm = NodeManager(config)
107+
assert len(nm.testrunuid) == 32
108+
109+
98110
class TestDistOptions:
99111
def test_getxspecs(self, testdir):
100112
config = testdir.parseconfigure("--tx=popen", "--tx", "ssh=xyz")

testing/test_remote.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import pprint
33
import pytest
44
import sys
5+
import uuid
56

67
from xdist.workermanage import WorkerController
78
import execnet
@@ -44,6 +45,7 @@ def setup(self,):
4445
putevent = self.use_callback and self.events.put or None
4546

4647
class DummyMananger:
48+
testrunuid = uuid.uuid4().hex
4749
specs = [0, 1]
4850

4951
self.slp = WorkerController(DummyMananger, self.gateway, config, putevent)
@@ -220,6 +222,7 @@ def test_remote_env_vars(testdir):
220222
"""
221223
import os
222224
def test():
225+
assert len(os.environ['PYTEST_XDIST_TESTRUNUID']) == 32
223226
assert os.environ['PYTEST_XDIST_WORKER'] in ('gw0', 'gw1')
224227
assert os.environ['PYTEST_XDIST_WORKER_COUNT'] == '2'
225228
"""

0 commit comments

Comments
 (0)