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

Commit

Permalink
Fix PyTest plugin when xdist is not installed
Browse files Browse the repository at this point in the history
The pytest_configure_node() hook is only allowed when xdist
is installed. This also updates the tests to run with and
without xdist.
  • Loading branch information
ramosbugs committed Sep 10, 2023
1 parent cd92dbb commit 6224680
Show file tree
Hide file tree
Showing 6 changed files with 182 additions and 323 deletions.
6 changes: 6 additions & 0 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -74,11 +74,17 @@ jobs:
- "3.11"
tox_env:
- pytest62
- pytest62-xdist
- pytest70
- pytest70-xdist
- pytest71
- pytest71-xdist
- pytest72
- pytest72-xdist
- pytest73
- pytest73-xdist
- pytest74
- pytest74-xdist

steps:
- uses: actions/checkout@v3
Expand Down
1 change: 0 additions & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@ dev =
flake8-quotes
mypy
py>=1.9.0
pytest-xdist>=2.0.0
requests-mock[fixture]
types-requests
types-setuptools
Expand Down
43 changes: 35 additions & 8 deletions src/pytest_unflakable/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,16 @@
import argparse

import os
import pprint
import sys
from typing import TYPE_CHECKING

import pytest
import logging

from ._api import get_test_suite_manifest
from ._git import get_current_git_commit, get_current_git_branch
from ._plugin import UnflakablePlugin, QuarantineMode
from ._plugin import UnflakablePlugin, QuarantineMode, UnflakableXdistHooks

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

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

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

insecure_disable_tls_validation = config.getoption(
'unflakable_insecure_disable_tls_validation', False)
manifest = None
if is_xdist_worker and 'unflakable_manifest' in config.workerinput: # type: ignore
worker_manifest = config.workerinput['unflakable_manifest'] # type: ignore
manifest = config.workerinput['unflakable_manifest'] # type: ignore
logger.debug(
f'xdist worker received manifest for test suite {test_suite_id}: '
f'{pprint.pformat(manifest)}'
)
else:
worker_manifest = None
try:
manifest = get_test_suite_manifest(
test_suite_id=test_suite_id,
api_key=api_key,
base_url=config.option.unflakable_base_url,
insecure_disable_tls_validation=insecure_disable_tls_validation,
logger=logger,
)
# IOError is the base class for `requests.RequestException`.
except IOError as e:
sys.stderr.write(
('ERROR: Failed to get Unflakable manifest: %s\nTest failures will NOT be'
' quarantined.\n') % (repr(e))),

if config.pluginmanager.hasplugin('xdist'):
config.pluginmanager.register(
UnflakableXdistHooks(logger=logger, worker_manifest=manifest)
)

config.pluginmanager.register(UnflakablePlugin(
api_key=api_key,
base_url=config.option.unflakable_base_url,
branch=branch,
commit=commit,
failure_retries=config.option.unflakable_failure_retries,
insecure_disable_tls_validation=config.getoption(
'unflakable_insecure_disable_tls_validation', False),
insecure_disable_tls_validation=insecure_disable_tls_validation,
quarantine_mode=quarantine_mode,
test_suite_id=config.option.unflakable_suite_id,
test_suite_id=test_suite_id,
upload_results=not is_xdist_worker and (
not config.getoption('unflakable_no_upload_results', False)),
logger=logger,
worker_manifest=worker_manifest,
manifest=manifest,
is_xdist_worker=is_xdist_worker,
))
62 changes: 26 additions & 36 deletions src/pytest_unflakable/_plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,13 @@
)

import logging
import pprint

import pytest
import _pytest
import sys
from time import time
from datetime import datetime, timezone

from ._api import (
get_test_suite_manifest,
create_test_suite_run,
build_test_suite_run_url,
CreateTestSuiteRunRequest,
Expand Down Expand Up @@ -152,6 +149,30 @@ def count_towards_summary(self) -> bool:
ItemReports = Dict[CallPhase, UnflakableReport]


class UnflakableXdistHooks:
logger: logging.Logger

def __init__(
self,
logger: logging.Logger,
worker_manifest: Optional[TestSuiteManifest],
):
self.logger = logger
self.manifest = worker_manifest

# This is a `xdist.workermanage.WorkerController`, but pytest-xdist doesn't provide types.
def pytest_configure_node(self, node: Any) -> None:
"""
Hook called by pytest-xdist to configure each worker node.
We leverage this hook to send the manifest to the worker.
"""
nodeid = node.workerinput['workerid']
self.logger.debug(f'called hook pytest_configure_node: {nodeid}')
if self.manifest is not None:
node.workerinput['unflakable_manifest'] = self.manifest


class UnflakablePlugin:
api_key: str
base_url: Optional[str]
Expand Down Expand Up @@ -186,7 +207,7 @@ def __init__(
test_suite_id: str,
upload_results: bool,
logger: logging.Logger,
worker_manifest: Optional[TestSuiteManifest],
manifest: Optional[TestSuiteManifest],
is_xdist_worker: bool,
):
self.api_key = api_key
Expand All @@ -203,26 +224,7 @@ def __init__(
self.is_xdist_worker = is_xdist_worker
self.item_reports = {}

self.manifest = worker_manifest
if self.manifest is None:
try:
self.manifest = get_test_suite_manifest(
test_suite_id=self.test_suite_id,
api_key=self.api_key,
base_url=self.base_url,
insecure_disable_tls_validation=self.insecure_disable_tls_validation,
logger=self.logger,
)
# IOError is the base class for `requests.RequestException`.
except IOError as e:
sys.stderr.write(
('ERROR: Failed to get Unflakable manifest: %s\nTest failures will NOT be'
' quarantined.\n') % (repr(e))),
else:
logger.debug(
f'xdist worker received manifest for test suite {self.test_suite_id}: '
f'{pprint.pformat(self.manifest)}'
)
self.manifest = manifest

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

# This is a `xdist.workermanage.WorkerController`, but pytest-xdist doesn't provide types.
def pytest_configure_node(self, node: Any) -> None:
"""
Hook called by pytest-xdist to configure each worker node.
We leverage this hook to send the manifest to the worker.
"""
nodeid = node.workerinput['workerid']
self.logger.debug(f'called hook pytest_configure_node: {nodeid}')
if self.manifest is not None:
node.workerinput['unflakable_manifest'] = self.manifest

def _build_test_suite_run_request(
self,
session: pytest.Session,
Expand Down
Loading

0 comments on commit 6224680

Please sign in to comment.