Skip to content
This repository was archived by the owner on Sep 1, 2024. It is now read-only.

Commit 6224680

Browse files
committed
Fix PyTest plugin when xdist is not installed
The pytest_configure_node() hook is only allowed when xdist is installed. This also updates the tests to run with and without xdist.
1 parent cd92dbb commit 6224680

File tree

6 files changed

+182
-323
lines changed

6 files changed

+182
-323
lines changed

.github/workflows/ci.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,11 +74,17 @@ jobs:
7474
- "3.11"
7575
tox_env:
7676
- pytest62
77+
- pytest62-xdist
7778
- pytest70
79+
- pytest70-xdist
7880
- pytest71
81+
- pytest71-xdist
7982
- pytest72
83+
- pytest72-xdist
8084
- pytest73
85+
- pytest73-xdist
8186
- pytest74
87+
- pytest74-xdist
8288

8389
steps:
8490
- uses: actions/checkout@v3

setup.cfg

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,6 @@ dev =
4747
flake8-quotes
4848
mypy
4949
py>=1.9.0
50-
pytest-xdist>=2.0.0
5150
requests-mock[fixture]
5251
types-requests
5352
types-setuptools

src/pytest_unflakable/__init__.py

Lines changed: 35 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,16 @@
55
import argparse
66

77
import os
8+
import pprint
9+
import sys
810
from typing import TYPE_CHECKING
911

1012
import pytest
1113
import logging
1214

15+
from ._api import get_test_suite_manifest
1316
from ._git import get_current_git_commit, get_current_git_branch
14-
from ._plugin import UnflakablePlugin, QuarantineMode
17+
from ._plugin import UnflakablePlugin, QuarantineMode, UnflakableXdistHooks
1518

1619
if TYPE_CHECKING:
1720
Config = pytest.Config
@@ -130,7 +133,7 @@ def pytest_configure(config: Config) -> None:
130133
if config.getoption('unflakable_suite_id') is None:
131134
raise pytest.UsageError('missing required argument --test-suite-id')
132135

133-
# pytest-xdist workers don't make API calls and amy not have the API key available.
136+
# pytest-xdist workers don't make API calls and may not have the API key available.
134137
if is_xdist_worker:
135138
api_key = ''
136139
elif config.option.unflakable_api_key_path is not None:
@@ -143,6 +146,7 @@ def pytest_configure(config: Config) -> None:
143146

144147
branch = config.option.unflakable_branch
145148
commit = config.option.unflakable_commit
149+
test_suite_id = config.option.unflakable_suite_id
146150
git_auto_detect = not config.getoption('unflakable_no_git_auto_detect', False)
147151
if git_auto_detect and not is_xdist_worker:
148152
if commit is None:
@@ -153,24 +157,47 @@ def pytest_configure(config: Config) -> None:
153157
branch = get_current_git_branch(commit, logger)
154158
logger.debug('auto-detected branch `%s`', branch)
155159

160+
insecure_disable_tls_validation = config.getoption(
161+
'unflakable_insecure_disable_tls_validation', False)
162+
manifest = None
156163
if is_xdist_worker and 'unflakable_manifest' in config.workerinput: # type: ignore
157-
worker_manifest = config.workerinput['unflakable_manifest'] # type: ignore
164+
manifest = config.workerinput['unflakable_manifest'] # type: ignore
165+
logger.debug(
166+
f'xdist worker received manifest for test suite {test_suite_id}: '
167+
f'{pprint.pformat(manifest)}'
168+
)
158169
else:
159-
worker_manifest = None
170+
try:
171+
manifest = get_test_suite_manifest(
172+
test_suite_id=test_suite_id,
173+
api_key=api_key,
174+
base_url=config.option.unflakable_base_url,
175+
insecure_disable_tls_validation=insecure_disable_tls_validation,
176+
logger=logger,
177+
)
178+
# IOError is the base class for `requests.RequestException`.
179+
except IOError as e:
180+
sys.stderr.write(
181+
('ERROR: Failed to get Unflakable manifest: %s\nTest failures will NOT be'
182+
' quarantined.\n') % (repr(e))),
183+
184+
if config.pluginmanager.hasplugin('xdist'):
185+
config.pluginmanager.register(
186+
UnflakableXdistHooks(logger=logger, worker_manifest=manifest)
187+
)
160188

161189
config.pluginmanager.register(UnflakablePlugin(
162190
api_key=api_key,
163191
base_url=config.option.unflakable_base_url,
164192
branch=branch,
165193
commit=commit,
166194
failure_retries=config.option.unflakable_failure_retries,
167-
insecure_disable_tls_validation=config.getoption(
168-
'unflakable_insecure_disable_tls_validation', False),
195+
insecure_disable_tls_validation=insecure_disable_tls_validation,
169196
quarantine_mode=quarantine_mode,
170-
test_suite_id=config.option.unflakable_suite_id,
197+
test_suite_id=test_suite_id,
171198
upload_results=not is_xdist_worker and (
172199
not config.getoption('unflakable_no_upload_results', False)),
173200
logger=logger,
174-
worker_manifest=worker_manifest,
201+
manifest=manifest,
175202
is_xdist_worker=is_xdist_worker,
176203
))

src/pytest_unflakable/_plugin.py

Lines changed: 26 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,13 @@
88
)
99

1010
import logging
11-
import pprint
1211

1312
import pytest
1413
import _pytest
15-
import sys
1614
from time import time
1715
from datetime import datetime, timezone
1816

1917
from ._api import (
20-
get_test_suite_manifest,
2118
create_test_suite_run,
2219
build_test_suite_run_url,
2320
CreateTestSuiteRunRequest,
@@ -152,6 +149,30 @@ def count_towards_summary(self) -> bool:
152149
ItemReports = Dict[CallPhase, UnflakableReport]
153150

154151

152+
class UnflakableXdistHooks:
153+
logger: logging.Logger
154+
155+
def __init__(
156+
self,
157+
logger: logging.Logger,
158+
worker_manifest: Optional[TestSuiteManifest],
159+
):
160+
self.logger = logger
161+
self.manifest = worker_manifest
162+
163+
# This is a `xdist.workermanage.WorkerController`, but pytest-xdist doesn't provide types.
164+
def pytest_configure_node(self, node: Any) -> None:
165+
"""
166+
Hook called by pytest-xdist to configure each worker node.
167+
168+
We leverage this hook to send the manifest to the worker.
169+
"""
170+
nodeid = node.workerinput['workerid']
171+
self.logger.debug(f'called hook pytest_configure_node: {nodeid}')
172+
if self.manifest is not None:
173+
node.workerinput['unflakable_manifest'] = self.manifest
174+
175+
155176
class UnflakablePlugin:
156177
api_key: str
157178
base_url: Optional[str]
@@ -186,7 +207,7 @@ def __init__(
186207
test_suite_id: str,
187208
upload_results: bool,
188209
logger: logging.Logger,
189-
worker_manifest: Optional[TestSuiteManifest],
210+
manifest: Optional[TestSuiteManifest],
190211
is_xdist_worker: bool,
191212
):
192213
self.api_key = api_key
@@ -203,26 +224,7 @@ def __init__(
203224
self.is_xdist_worker = is_xdist_worker
204225
self.item_reports = {}
205226

206-
self.manifest = worker_manifest
207-
if self.manifest is None:
208-
try:
209-
self.manifest = get_test_suite_manifest(
210-
test_suite_id=self.test_suite_id,
211-
api_key=self.api_key,
212-
base_url=self.base_url,
213-
insecure_disable_tls_validation=self.insecure_disable_tls_validation,
214-
logger=self.logger,
215-
)
216-
# IOError is the base class for `requests.RequestException`.
217-
except IOError as e:
218-
sys.stderr.write(
219-
('ERROR: Failed to get Unflakable manifest: %s\nTest failures will NOT be'
220-
' quarantined.\n') % (repr(e))),
221-
else:
222-
logger.debug(
223-
f'xdist worker received manifest for test suite {self.test_suite_id}: '
224-
f'{pprint.pformat(self.manifest)}'
225-
)
227+
self.manifest = manifest
226228

227229
self.quarantined_tests = set([
228230
(quarantined_test['filename'], tuple(quarantined_test['name'])) for
@@ -495,18 +497,6 @@ def pytest_sessionstart(self, session: pytest.Session) -> None:
495497
self.session = session
496498
self.start_time = time()
497499

498-
# This is a `xdist.workermanage.WorkerController`, but pytest-xdist doesn't provide types.
499-
def pytest_configure_node(self, node: Any) -> None:
500-
"""
501-
Hook called by pytest-xdist to configure each worker node.
502-
503-
We leverage this hook to send the manifest to the worker.
504-
"""
505-
nodeid = node.workerinput['workerid']
506-
self.logger.debug(f'called hook pytest_configure_node: {nodeid}')
507-
if self.manifest is not None:
508-
node.workerinput['unflakable_manifest'] = self.manifest
509-
510500
def _build_test_suite_run_request(
511501
self,
512502
session: pytest.Session,

0 commit comments

Comments
 (0)