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

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. 

26 

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. 

31 

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 

36 

37Requires network access, to install the wheel's dependencies. 

38""" 

39from __future__ import annotations 

40 

41from dataclasses import dataclass 

42import os 

43from pathlib import Path 

44import subprocess 

45from typing import * 

46import venv 

47 

48import pytest 

49 

50 

51WHEEL_ENV_VAR = 'KPEX_SMOKE_TEST_WHEEL' 

52 

53SMOKE_TESTS_DIR = Path(__file__).resolve().parent 

54 

55 

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) 

62 

63 

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 

69 

70 

71@dataclass(frozen=True) 

72class InstalledWheel: 

73 venv_dir: Path 

74 

75 @property 

76 def bin_dir(self) -> Path: 

77 return self.venv_dir / ('Scripts' if os.name == 'nt' else 'bin') 

78 

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) 

82 

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) 

92 

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 

99 

100 

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" 

109 

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) 

114 

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