Coverage for tests/integrations/conftest.py: 69%
51 statements
« prev ^ index » next coverage.py v7.2.7, created at 2024-08-27 18:25 +0000
« prev ^ index » next coverage.py v7.2.7, created at 2024-08-27 18:25 +0000
1from __future__ import annotations
3import sys
4import shutil
5import subprocess
6from typing import TYPE_CHECKING, Any, Union, Iterator, Optional, cast
7from pathlib import Path
8from functools import lru_cache
10import pytest
11from _pytest._code import ExceptionInfo
12from _pytest.nodes import Node
13from _pytest.config import Config
14from _pytest._code.code import TerminalRepr
16if TYPE_CHECKING:
17 from _pytest._code.code import _TracebackStyle
20ROOTDIR = Path(__file__).parent.parent.parent
23class IntegrationError(AssertionError):
24 def __init__(self, message: Optional[str] = None, lineno: int = 0) -> None: # pragma: no cover
25 self.message = message or ''
26 self.lineno = lineno
27 super().__init__()
29 def __str__(self) -> str: # pragma: no cover
30 return self.message
33def resolve_path(path: Path) -> Path:
34 return path.relative_to(Path.cwd())
37def is_integration_test_file(path: Path) -> bool:
38 # disable integration tests as they are currently broken
39 return False
42def pytest_ignore_collect(collection_path: Path, config: Config) -> Optional[bool]:
43 """We need to ignore any integration test sub-paths
45 For example we need to include
47 tests/integrations
48 tests/integrations/*
49 tests/integrations/basic/test.sh
51 but ignore anything that looks like
53 tests/integrations/basic/*
54 tests/integrations/basic/conftest.py
55 tests/integrations/basic/tests/test_foo.py
56 """
57 path = resolve_path(collection_path)
58 if path.parts[:2] != ('tests', 'integrations'): # pragma: no cover
59 # not an integration test
60 return None
62 if len(path.parts) <= 3: 62 ↛ 66line 62 didn't jump to line 66, because the condition on line 62 was never false
63 # integration root dir, leave handling to pytest
64 return None
66 return path.parts[-1] != 'test.sh'
69def pytest_collect_file(file_path: Path, parent: Node) -> Optional['IntegrationTestFile']:
70 path = resolve_path(file_path)
71 if path.suffix == '.sh' and is_integration_test_file(path) and sys.platform != 'win32': 71 ↛ 72line 71 didn't jump to line 72, because the condition on line 71 was never true
72 return IntegrationTestFile.from_parent(parent, path=path)
74 return None
77@lru_cache(maxsize=None)
78def create_wheels() -> None:
79 dist = ROOTDIR / '.tests_cache' / 'dist'
80 if dist.exists(): # pragma: no cover
81 shutil.rmtree(str(dist))
83 result = subprocess.run(
84 [
85 sys.executable,
86 'setup.py',
87 'bdist_wheel',
88 '--dist-dir=.tests_cache/dist',
89 ],
90 check=False,
91 stdout=subprocess.PIPE,
92 stderr=subprocess.STDOUT,
93 cwd=str(ROOTDIR),
94 )
95 if result.returncode != 0: # pragma: no cover
96 print(result.stdout.decode('utf-8'))
97 raise RuntimeError('Could not build wheels, see output above.')
100class IntegrationTestItem(pytest.Item):
101 def __init__(
102 self,
103 name: str,
104 parent: Optional['IntegrationTestFile'] = None,
105 config: Optional[Config] = None,
106 *,
107 path: Path,
108 ) -> None:
109 super().__init__(name, parent, config)
110 self.path = path
111 self.starting_lineno = 1
113 def setup(self) -> None:
114 create_wheels()
116 def runtest(self) -> None:
117 # TODO: include exit code in pytest failure short description
118 result = subprocess.run(
119 [str(self.path)],
120 cwd=str(self.path.parent),
121 stdout=subprocess.PIPE,
122 stderr=subprocess.STDOUT,
123 check=False,
124 )
125 print(result.stdout.decode('utf-8'))
126 if result.returncode != 0: # pragma: no cover
127 raise IntegrationError(f'Executing `{self.path}` returned non-zero exit code {result.returncode}')
129 def repr_failure(
130 self,
131 excinfo: ExceptionInfo[BaseException],
132 style: Optional['_TracebackStyle'] = None,
133 ) -> Union[str, TerminalRepr]: # pragma: no cover
134 if isinstance(excinfo.value, IntegrationError):
135 return f'IntegrationError: {excinfo.value.message}'
137 return super().repr_failure(excinfo, style=style)
139 def reportinfo(self):
140 return self.fspath, 0, f'integration: {self.name}'
143class IntegrationTestFile(pytest.File):
144 @classmethod
145 def from_parent(cls, *args: Any, **kwargs: Any) -> 'IntegrationTestFile':
146 return cast(
147 IntegrationTestFile,
148 super().from_parent(*args, **kwargs), # type: ignore[no-untyped-call]
149 )
151 def collect(self) -> Iterator[IntegrationTestItem]:
152 path = Path(self.fspath)
153 yield IntegrationTestItem.from_parent(parent=self, name=path.parent.name, path=path)