Coverage for tests/smoke/pdk_extraction_test.py: 46%
37 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: `kpex extract --pdk <PDK>` works for each bundled PDK (e.g. all rule decks are packaged).
27Requires the KLayout executable (see KPEX_KLAYOUT_EXE), see conftest.py for running the smoke tests.
28"""
29from __future__ import annotations
31from pathlib import Path
32from typing import *
34import allure
35import pytest
37from klayout_pex.pdk_config import PDK
39if TYPE_CHECKING:
40 from .conftest import InstalledWheel # NOTE: the `installed_wheel` fixture
43TESTDATA_DESIGNS_DIR = Path(__file__).resolve().parents[2] / 'testdata' / 'designs'
45# NOTE: one small layout per bundled PDK, each PDK needs one
46SMOKE_TEST_DESIGNS: Dict[PDK, Path] = {
47 PDK.GF180MCUD: TESTDATA_DESIGNS_DIR / 'gf180mcuD' / 'test_patterns' / 'nfet_m1.gds.gz',
48 PDK.IHP_SG13G2: TESTDATA_DESIGNS_DIR / 'ihp-sg13g2' / 'sg13g2_a21o_1' / 'sg13g2_a21o_1.gds.gz',
49 PDK.IHP_SG13CMOS5L: TESTDATA_DESIGNS_DIR / 'ihp-sg13cmos5l' / 'cap_cmomf_w5u_l5u_m1_m4'
50 / 'cap_cmomf_w5u_l5u_m1_m4.gds.gz',
51 PDK.SKY130A: TESTDATA_DESIGNS_DIR / 'sky130A' / 'inv' / 'inv.gds.gz',
52}
54parent_suite = "Smoke Tests (installed wheel)"
55suite = "Bundled PDKs"
56tags = ("Smoke", "Wheel", "Packaging", "PDK")
59def _tail(text: str, lines: int = 40) -> str:
60 return '\n'.join(text.splitlines()[-lines:])
63@allure.parent_suite(parent_suite)
64@allure.suite(suite)
65@allure.tag(*tags)
66@pytest.mark.parametrize('pdk', list(PDK), ids=str)
67def test_kpex_extract(pdk: PDK, installed_wheel: InstalledWheel, tmp_path: Path):
68 assert pdk in SMOKE_TEST_DESIGNS, f"No smoke test design for PDK {pdk}, please add one to SMOKE_TEST_DESIGNS"
69 gds_path = SMOKE_TEST_DESIGNS[pdk]
70 assert gds_path.is_file(), f"Smoke test design is missing: {gds_path}"
72 out_dir = tmp_path / 'out'
73 proc = installed_wheel.run('kpex', 'extract',
74 '--pdk', pdk,
75 '--gds', gds_path,
76 '--2.5D',
77 '--out_dir', out_dir,
78 cwd=tmp_path)
79 allure.attach(proc.stdout + proc.stderr, name='kpex output', attachment_type=allure.attachment_type.TEXT)
81 # NOTE: output files are within <out_dir>/<gds stem>__<cell>/
82 lvs_logs = sorted(out_dir.glob('*/*_lvs.log'))
83 lvsdbs = sorted(out_dir.glob('*/*.lvsdb.gz'))
84 netlists = sorted(out_dir.glob('*/*_k25d_pex_netlist.spice'))
86 diagnostics = f"--- kpex output (tail):\n{_tail(proc.stdout + proc.stderr)}"
87 for lvs_log in lvs_logs:
88 lvs_log_text = lvs_log.read_text(errors='replace')
89 allure.attach(lvs_log_text, name=lvs_log.name, attachment_type=allure.attachment_type.TEXT)
90 errors = [line for line in lvs_log_text.splitlines() if 'ERROR' in line]
91 diagnostics += f"\n--- ERROR lines of {lvs_log}:\n" + '\n'.join(errors[:20])
93 returncode = proc.returncode
94 assert returncode == 0, f"kpex failed with status {returncode}\n{diagnostics}"
95 assert len(lvsdbs) == 1, f"Expected 1 LVSDB, got {lvsdbs}\n{diagnostics}"
96 assert len(netlists) == 1 and netlists[0].stat().st_size > 0, \
97 f"Expected 1 non-empty extracted netlist, got {netlists}\n{diagnostics}"