Coverage for tests/smoke/conftest.py: 57%
46 statements
« prev ^ index » next coverage.py v7.16.2, created at 2026-10-02 23:32 +0000
« prev ^ index » next coverage.py v7.16.2, created at 2026-10-02 23:32 +0000
1#
2# --------------------------------------------------------------------------------
3# SPDX-FileCopyrightText: 2024-2025 Martin Jan Köhler and Harald Pretl
4# Johannes Kepler University, Institute for Integrated Circuits.
5#
6# This file is part of KPEX
7# (see https://github.com/iic-jku/klayout-pex).
8#
9# This program is free software: you can redistribute it and/or modify
10# it under the terms of the GNU General Public License as published by
11# the Free Software Foundation, either version 3 of the License, or
12# (at your option) any later version.
13#
14# This program is distributed in the hope that it will be useful,
15# but WITHOUT ANY WARRANTY; without even the implied warranty of
16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17# GNU General Public License for more details.
18#
19# You should have received a copy of the GNU General Public License
20# along with this program. If not, see <http://www.gnu.org/licenses/>.
21# SPDX-License-Identifier: GPL-3.0-or-later
22# --------------------------------------------------------------------------------
23#
24"""
25Smoke test suite: tests of the *installed* wheel, one module per aspect.
27Tests running from the source checkout can't catch packaging bugs, e.g. rule decks
28missing from the wheel (https://github.com/iic-jku/klayout-pex/issues/210),
29as the checkout has all files. So the `installed_wheel` fixture installs the wheel
30into a fresh venv, and smoke tests run its console scripts outside of the checkout.
32All tests of this suite are marked 'smoke', and skipped unless KPEX_SMOKE_TEST_WHEEL points to the wheel.
33Locally, run_smoke_tests.sh builds the wheel and runs the suite, or manually:
34 poetry build
35 KPEX_SMOKE_TEST_WHEEL=dist/klayout_pex-<version>-py3-none-any.whl poetry run pytest -m smoke
37Requires network access, to install the wheel's dependencies.
38"""
39from __future__ import annotations
41from dataclasses import dataclass
42import os
43from pathlib import Path
44import subprocess
45from typing import *
46import venv
48import pytest
51WHEEL_ENV_VAR = 'KPEX_SMOKE_TEST_WHEEL'
53SMOKE_TESTS_DIR = Path(__file__).resolve().parent
56@pytest.hookimpl(tryfirst=True) # NOTE: before `-m` deselects by marker
57def pytest_collection_modifyitems(config, items):
58 """Marks all tests of this suite as 'smoke' (including future modules)"""
59 for item in items:
60 if Path(item.path).resolve().is_relative_to(SMOKE_TESTS_DIR):
61 item.add_marker(pytest.mark.smoke)
64def _isolated_env() -> Dict[str, str]:
65 """Environment for processes of the wheel venv, which must not see the source checkout"""
66 env = dict(os.environ)
67 env.pop('PYTHONPATH', None)
68 return env
71@dataclass(frozen=True)
72class InstalledWheel:
73 venv_dir: Path
75 @property
76 def bin_dir(self) -> Path:
77 return self.venv_dir / ('Scripts' if os.name == 'nt' else 'bin')
79 def executable(self, name: str) -> Path:
80 """A console script (e.g. 'kpex') or 'python' of the venv"""
81 return self.bin_dir / (f"{name}.exe" if os.name == 'nt' else name)
83 def run(self,
84 name: str,
85 *args: Any,
86 cwd: Path,
87 timeout: float = 600) -> subprocess.CompletedProcess:
88 """Runs a console script (or 'python') of the venv, the caller checks the return code"""
89 return subprocess.run([str(self.executable(name)), *(str(a) for a in args)],
90 cwd=cwd, env=_isolated_env(), stdin=subprocess.DEVNULL,
91 capture_output=True, text=True, timeout=timeout)
93 def check_run(self, name: str, *args: Any, cwd: Path) -> subprocess.CompletedProcess:
94 proc = self.run(name, *args, cwd=cwd)
95 returncode = proc.returncode
96 assert returncode == 0, \
97 f"Command failed with status {returncode}: {' '.join(proc.args)}\n{proc.stdout}\n{proc.stderr}"
98 return proc
101@pytest.fixture(scope='session')
102def installed_wheel(tmp_path_factory) -> InstalledWheel:
103 """A fresh venv with the wheel (KPEX_SMOKE_TEST_WHEEL) installed"""
104 if WHEEL_ENV_VAR not in os.environ:
105 pytest.skip(f"{WHEEL_ENV_VAR} is not set")
106 wheel_path = Path(os.environ[WHEEL_ENV_VAR]).resolve()
107 assert wheel_path.is_file() and wheel_path.suffix == '.whl', \
108 f"{WHEEL_ENV_VAR}={os.environ[WHEEL_ENV_VAR]} is not a wheel file"
110 wheel = InstalledWheel(venv_dir=tmp_path_factory.mktemp('installed_wheel'))
111 venv.create(wheel.venv_dir, with_pip=True)
112 wheel.check_run('python', '-m', 'pip', 'install', '--quiet', '--disable-pip-version-check', wheel_path,
113 cwd=wheel.venv_dir)
115 # NOTE: guard against testing the source checkout by accident
116 module_path = wheel.check_run('python', '-c', 'import klayout_pex; print(klayout_pex.__file__)',
117 cwd=wheel.venv_dir).stdout.strip()
118 assert Path(module_path).resolve().is_relative_to(wheel.venv_dir.resolve()), \
119 f"klayout_pex is not imported from the wheel venv {wheel.venv_dir}, but from {module_path}"
120 return wheel