Skip to content

Commit 0c02220

Browse files
authored
Support for running with xdist disabled or not installed (#10)
Fixes #9
1 parent a7a700a commit 0c02220

File tree

6 files changed

+77
-24
lines changed

6 files changed

+77
-24
lines changed

.flake8

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
[flake8]
2+
max-line-length = 88
3+
extend-ignore = E203

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,4 @@ dist
66
__pycache__
77
.python-version
88
*.egg-info
9+
.vscode

setup.cfg

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[metadata]
22
name = pytest-opentelemetry
3-
version = 0.3.2
3+
version = 0.4.0
44
author = Chris Guidry
55
author_email = [email protected]
66
description = A pytest plugin for instrumenting test runs via OpenTelemetry

src/pytest_opentelemetry/instrumentation.py

Lines changed: 36 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -17,32 +17,23 @@
1717

1818
from .resource import CodebaseResourceDetector
1919

20-
try:
21-
from xdist.workermanage import WorkerController # pylint: disable=unused-import
22-
except ImportError: # pragma: no cover
23-
WorkerController = None
24-
2520
tracer = trace.get_tracer('pytest-opentelemetry')
2621

2722

2823
class OpenTelemetryPlugin:
2924
"""A pytest plugin which produces OpenTelemetry spans around test sessions and
3025
individual test runs."""
3126

32-
@staticmethod
33-
def get_trace_parent(config: Config) -> Optional[Context]:
27+
@classmethod
28+
def get_trace_parent(cls, config: Config) -> Optional[Context]:
3429
if trace_parent := config.getvalue('--trace-parent'):
3530
from_arguments = {'traceparent': trace_parent}
3631
return propagate.extract(from_arguments)
3732

38-
if workerinput := getattr(config, 'workerinput', None):
39-
return propagate.extract(workerinput)
40-
4133
return None
4234

4335
def pytest_configure(self, config: Config) -> None:
4436
self.trace_parent = self.get_trace_parent(config)
45-
self.worker_id = getattr(config, 'workerinput', {}).get('workerid')
4637

4738
# This can't be tested both ways in one process
4839
if config.getoption('--export-traces'): # pragma: no cover
@@ -52,13 +43,13 @@ def pytest_configure(self, config: Config) -> None:
5243
configurator.resource_detectors.append(CodebaseResourceDetector())
5344
configurator.configure()
5445

55-
def pytest_sessionstart(self, session: Session) -> None:
56-
session_name = f'test worker {self.worker_id}' if self.worker_id else 'test run'
57-
self.session_span = tracer.start_span(session_name, context=self.trace_parent)
46+
session_name: str = 'test run'
5847

59-
def pytest_configure_node(self, node: WorkerController) -> None: # pragma: no cover
60-
with trace.use_span(self.session_span, end_on_exit=False):
61-
propagate.inject(node.workerinput)
48+
def pytest_sessionstart(self, session: Session) -> None:
49+
self.session_span = tracer.start_span(
50+
self.session_name,
51+
context=self.trace_parent,
52+
)
6253

6354
def pytest_sessionfinish(self, session: Session) -> None:
6455
self.session_span.end()
@@ -110,3 +101,31 @@ def pytest_runtest_logreport(report: TestReport) -> None:
110101

111102
status_code = StatusCode.ERROR if report.outcome == 'failed' else StatusCode.OK
112103
trace.get_current_span().set_status(Status(status_code))
104+
105+
106+
try:
107+
from xdist.workermanage import WorkerController # pylint: disable=unused-import
108+
except ImportError: # pragma: no cover
109+
WorkerController = None
110+
111+
112+
class XdistOpenTelemetryPlugin(OpenTelemetryPlugin):
113+
"""An xdist-aware version of the OpenTelemetryPlugin"""
114+
115+
@classmethod
116+
def get_trace_parent(cls, config: Config) -> Optional[Context]:
117+
if workerinput := getattr(config, 'workerinput', None):
118+
return propagate.extract(workerinput)
119+
120+
return super().get_trace_parent(config)
121+
122+
def pytest_configure(self, config: Config) -> None:
123+
super().pytest_configure(config)
124+
worker_id = getattr(config, 'workerinput', {}).get('workerid')
125+
self.session_name = (
126+
f'test worker {worker_id}' if worker_id else self.session_name
127+
)
128+
129+
def pytest_configure_node(self, node: WorkerController) -> None: # pragma: no cover
130+
with trace.use_span(self.session_span, end_on_exit=False):
131+
propagate.inject(node.workerinput)

src/pytest_opentelemetry/plugin.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,12 @@ def pytest_addoption(parser: Parser) -> None:
3030

3131
def pytest_configure(config: Config) -> None:
3232
# pylint: disable=import-outside-toplevel
33-
from pytest_opentelemetry.instrumentation import OpenTelemetryPlugin
33+
from pytest_opentelemetry.instrumentation import (
34+
OpenTelemetryPlugin,
35+
XdistOpenTelemetryPlugin,
36+
)
3437

35-
config.pluginmanager.register(OpenTelemetryPlugin())
38+
if config.pluginmanager.has_plugin("xdist"):
39+
config.pluginmanager.register(XdistOpenTelemetryPlugin())
40+
else:
41+
config.pluginmanager.register(OpenTelemetryPlugin())

tests/test_sessions.py

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
from _pytest.pytester import Pytester
22
from opentelemetry import trace
33

4-
from pytest_opentelemetry.instrumentation import OpenTelemetryPlugin
4+
from pytest_opentelemetry.instrumentation import (
5+
OpenTelemetryPlugin,
6+
XdistOpenTelemetryPlugin,
7+
)
58

69
from . import SpanRecorder
710

@@ -35,7 +38,7 @@ def test_getting_trace_id_from_worker_input(pytester: Pytester) -> None:
3538
'workerinput',
3639
{'traceparent': '00-1234567890abcdef1234567890abcdef-fedcba0987654321-01'},
3740
)
38-
context = OpenTelemetryPlugin.get_trace_parent(config)
41+
context = XdistOpenTelemetryPlugin.get_trace_parent(config)
3942
assert context
4043

4144
parent_span = next(iter(context.values()))
@@ -86,15 +89,13 @@ def test_one(worker_id):
8689
8790
span = trace.get_current_span()
8891
assert span.context.trace_id == 0x1234567890abcdef1234567890abcdef
89-
assert span.context.span_id != 0xfedcba0987654321
9092
9193
def test_two(worker_id):
9294
# confirm that this is an xdist worker
9395
assert worker_id in {'gw0', 'gw1'}
9496
9597
span = trace.get_current_span()
9698
assert span.context.trace_id == 0x1234567890abcdef1234567890abcdef
97-
assert span.context.span_id != 0xfedcba0987654321
9899
"""
99100
)
100101
result = pytester.runpytest_subprocess(
@@ -104,3 +105,26 @@ def test_two(worker_id):
104105
'00-1234567890abcdef1234567890abcdef-fedcba0987654321-01',
105106
)
106107
result.assert_outcomes(passed=2)
108+
109+
110+
def test_works_without_xdist(pytester: Pytester, span_recorder: SpanRecorder) -> None:
111+
pytester.makepyfile(
112+
"""
113+
from opentelemetry import trace
114+
115+
def test_one():
116+
span = trace.get_current_span()
117+
assert span.context.trace_id == 0x1234567890abcdef1234567890abcdef
118+
119+
def test_two():
120+
span = trace.get_current_span()
121+
assert span.context.trace_id == 0x1234567890abcdef1234567890abcdef
122+
"""
123+
)
124+
result = pytester.runpytest_subprocess(
125+
'-p',
126+
'no:xdist',
127+
'--trace-parent',
128+
'00-1234567890abcdef1234567890abcdef-fedcba0987654321-01',
129+
)
130+
result.assert_outcomes(passed=2)

0 commit comments

Comments
 (0)