Skip to content

Commit 9d54745

Browse files
pytest markers and updated markdowns
1 parent c193179 commit 9d54745

9 files changed

Lines changed: 164 additions & 80 deletions

File tree

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,12 @@ All notable changes to this project will be documented in this file.
88

99
### Changed
1010

11+
- **Faster default pytest target**
12+
13+
Marked slow chemistry and subprocess CLI integration tests separately so
14+
`pytest -q` runs the fast development suite by default. The full integration
15+
suite remains available with `pytest -q -o addopts=''`.
16+
1117
- **Tightened public API reference**
1218

1319
Reworked the Sphinx API reference into task-oriented sections for primary

README.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -401,10 +401,19 @@ Variational_Quantum_Eigensolver/
401401

402402
## Testing
403403

404+
The default pytest target is the fast development suite. Slow chemistry and
405+
subprocess CLI integration tests are marked separately.
406+
404407
```bash
405408
pytest -q
406409
```
407410

411+
Run the full suite, including slow integration coverage, with:
412+
413+
```bash
414+
pytest -q -o addopts=''
415+
```
416+
408417
---
409418

410419
## Support development

USAGE.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -850,10 +850,19 @@ qite run-qrte --force
850850

851851
## Testing
852852

853+
The default pytest target runs the fast development suite. Slow chemistry and
854+
subprocess CLI integration tests are available behind pytest markers.
855+
853856
```bash
854857
pytest -q
855858
```
856859

860+
Run every test, including slow integration coverage:
861+
862+
```bash
863+
pytest -q -o addopts=''
864+
```
865+
857866
---
858867

859868
## Citation

pyproject.toml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,13 @@ docs = [
5353
]
5454

5555
[tool.pytest.ini_options]
56+
addopts = "-m 'not slow'"
57+
markers = [
58+
"slow: tests that are useful integration coverage but too expensive for the default PR loop",
59+
"chemistry: tests that build real chemistry Hamiltonians or invoke expensive quantum chemistry backends",
60+
"full_chemistry: broad chemistry coverage across many registered molecules",
61+
"cli_subprocess: tests that validate python -m entrypoints in a separate interpreter",
62+
]
5663
filterwarnings = [
5764
"error::pennylane.exceptions.PennyLaneDeprecationWarning",
5865
"error::DeprecationWarning",

qpe/__main__.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ def _validated_geometry_inputs(
8787
# ---------------------------------------------------------------------
8888
# Arguments
8989
# ---------------------------------------------------------------------
90-
def parse_args():
90+
def build_parser() -> argparse.ArgumentParser:
9191
parser = argparse.ArgumentParser(
9292
prog="qpe",
9393
description="Quantum Phase Estimation (QPE) simulator",
@@ -223,14 +223,18 @@ def parse_args():
223223
"--force", action="store_true", help="Force rerun even if cached result exists"
224224
)
225225

226-
return parser.parse_args()
226+
return parser
227+
228+
229+
def parse_args(argv: list[str] | None = None) -> argparse.Namespace:
230+
return build_parser().parse_args(argv)
227231

228232

229233
# ---------------------------------------------------------------------
230234
# Main logic
231235
# ---------------------------------------------------------------------
232-
def main():
233-
args = parse_args()
236+
def main(argv: list[str] | None = None):
237+
args = parse_args(argv)
234238
ensure_dirs()
235239
symbols, coordinates = _validated_geometry_inputs(args)
236240
molecule_label = (

tests/test_cli_help.py

Lines changed: 16 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@
33
import subprocess
44
import sys
55

6+
import pytest
7+
8+
import qite.__main__ as qite_main
9+
import qpe.__main__ as qpe_main
610
import vqe.__main__ as vqe_main
711

812

@@ -16,22 +20,20 @@ def _run_help(module: str) -> subprocess.CompletedProcess[str]:
1620
)
1721

1822

19-
def test_vqe_cli_help() -> None:
20-
p = _run_help("vqe")
21-
assert p.returncode == 0
22-
out = (p.stdout or "") + (p.stderr or "")
23-
assert "usage" in out.lower()
24-
25-
26-
def test_qpe_cli_help() -> None:
27-
p = _run_help("qpe")
28-
assert p.returncode == 0
29-
out = (p.stdout or "") + (p.stderr or "")
23+
@pytest.mark.parametrize(
24+
"build_parser",
25+
[vqe_main.build_parser, qpe_main.build_parser, qite_main.build_parser],
26+
)
27+
def test_cli_help_is_available_in_process(build_parser) -> None:
28+
out = build_parser().format_help()
3029
assert "usage" in out.lower()
3130

3231

33-
def test_qite_cli_help() -> None:
34-
p = _run_help("qite")
32+
@pytest.mark.slow
33+
@pytest.mark.cli_subprocess
34+
@pytest.mark.parametrize("module", ["vqe", "qpe", "qite"])
35+
def test_module_cli_help(module: str) -> None:
36+
p = _run_help(module)
3537
assert p.returncode == 0
3638
out = (p.stdout or "") + (p.stderr or "")
3739
assert "usage" in out.lower()
@@ -48,13 +50,7 @@ def fake_run_vqe(**kwargs):
4850
return {"energy": -1.0, "energies": [-1.0], "num_qubits": 1}
4951

5052
monkeypatch.setattr(vqe_main, "run_vqe", fake_run_vqe)
51-
monkeypatch.setattr(
52-
sys,
53-
"argv",
54-
["vqe", "--molecule", "H2", "--steps", "1", "--force"],
55-
)
56-
57-
vqe_main.main()
53+
vqe_main.main(["--molecule", "H2", "--steps", "1", "--force"])
5854
out = capsys.readouterr().out
5955

6056
assert captured["stepsize"] is None

tests/test_hamiltonian.py

Lines changed: 68 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,51 @@
66
from common.molecules import get_molecule_config
77
from common.units import ANGSTROM_PER_BOHR
88

9+
REPRESENTATIVE_REGISTRY_MOLECULES = [
10+
"H",
11+
"H2",
12+
"H2+",
13+
"H3+",
14+
"Li",
15+
"He",
16+
"HeH+",
17+
]
18+
19+
FULL_REGISTRY_MOLECULES = [
20+
"H",
21+
"H-",
22+
"He",
23+
"He+",
24+
"H2",
25+
"H2+",
26+
"H2-",
27+
"H3",
28+
"H3+",
29+
"Li",
30+
"Li+",
31+
"B",
32+
"B+",
33+
"C",
34+
"C+",
35+
"N",
36+
"N+",
37+
"O",
38+
"O+",
39+
"F",
40+
"F+",
41+
"Ne",
42+
"H4",
43+
"H4+",
44+
"Be",
45+
"Be+",
46+
"He2",
47+
"H5+",
48+
"H6",
49+
"LiH",
50+
"BeH2",
51+
"HeH+",
52+
]
53+
954

1055
def test_build_hamiltonian_h2() -> None:
1156
cfg = get_molecule_config("H2")
@@ -20,47 +65,27 @@ def test_build_hamiltonian_h2() -> None:
2065
assert all(hasattr(op, "wires") for op in ops)
2166

2267

23-
def test_build_hamiltonian_selected_registry_molecules() -> None:
24-
for name in [
25-
"H",
26-
"H-",
27-
"He",
28-
"He+",
29-
"H2",
30-
"H2+",
31-
"H2-",
32-
"H3",
33-
"H3+",
34-
"Li",
35-
"Li+",
36-
"B",
37-
"B+",
38-
"C",
39-
"C+",
40-
"N",
41-
"N+",
42-
"O",
43-
"O+",
44-
"F",
45-
"F+",
46-
"Ne",
47-
"H4",
48-
"H4+",
49-
"Be",
50-
"Be+",
51-
"He2",
52-
"H5+",
53-
"H6",
54-
"LiH",
55-
"BeH2",
56-
"HeH+",
57-
]:
58-
cfg = get_molecule_config(name)
59-
hamiltonian, n_qubits, hf_state = build_hamiltonian(**cfg)
60-
61-
assert n_qubits > 0
62-
assert len(hf_state) == n_qubits
63-
assert len(hamiltonian) > 0
68+
@pytest.mark.parametrize("name", REPRESENTATIVE_REGISTRY_MOLECULES)
69+
def test_build_hamiltonian_representative_registry_molecules(name: str) -> None:
70+
cfg = get_molecule_config(name)
71+
hamiltonian, n_qubits, hf_state = build_hamiltonian(**cfg)
72+
73+
assert n_qubits > 0
74+
assert len(hf_state) == n_qubits
75+
assert len(hamiltonian) > 0
76+
77+
78+
@pytest.mark.slow
79+
@pytest.mark.chemistry
80+
@pytest.mark.full_chemistry
81+
@pytest.mark.parametrize("name", FULL_REGISTRY_MOLECULES)
82+
def test_build_hamiltonian_full_registry_molecules(name: str) -> None:
83+
cfg = get_molecule_config(name)
84+
hamiltonian, n_qubits, hf_state = build_hamiltonian(**cfg)
85+
86+
assert n_qubits > 0
87+
assert len(hf_state) == n_qubits
88+
assert len(hamiltonian) > 0
6489

6590

6691
def test_registry_mode_rejects_basis_and_charge_overrides() -> None:
@@ -162,6 +187,8 @@ def test_summarize_registry_coverage_returns_expected_rows() -> None:
162187
assert "exact_ground_energy" in h2_row
163188

164189

190+
@pytest.mark.slow
191+
@pytest.mark.chemistry
165192
def test_active_space_reduces_lih_qubit_count() -> None:
166193
full_hamiltonian, full_qubits, full_hf = build_hamiltonian(molecule="LiH")
167194
active_hamiltonian, active_qubits, active_hf = build_hamiltonian(

tests/test_qpe_smoke.py

Lines changed: 34 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import numpy as np
99

1010
from common.hamiltonian import build_hamiltonian
11+
import qpe.__main__ as qpe_main
1112
from qpe import run_qpe
1213
from qpe.visualize import plot_qpe_distribution
1314

@@ -189,12 +190,23 @@ def test_qpe_distribution_displays_right_to_left_kets(monkeypatch) -> None:
189190
assert labels == ["|00⟩", "|01⟩", "|10⟩", "|11⟩"]
190191

191192

192-
def test_qpe_cli_supports_explicit_geometry() -> None:
193-
p = subprocess.run(
193+
def test_qpe_cli_supports_explicit_geometry(monkeypatch, capsys) -> None:
194+
captured: dict[str, object] = {}
195+
196+
def fake_run_qpe(**kwargs):
197+
captured.update(kwargs)
198+
return {
199+
"best_bitstring": "0",
200+
"energy": -1.0,
201+
"hf_energy": -0.9,
202+
"num_qubits": 4,
203+
}
204+
205+
monkeypatch.setattr(qpe_main, "ensure_dirs", lambda: None)
206+
monkeypatch.setattr(qpe_main, "run_qpe", fake_run_qpe)
207+
208+
qpe_main.main(
194209
[
195-
sys.executable,
196-
"-m",
197-
"qpe",
198210
"--symbols",
199211
"H,H",
200212
"--coordinates",
@@ -208,18 +220,27 @@ def test_qpe_cli_supports_explicit_geometry() -> None:
208220
"--shots",
209221
"50",
210222
"--force",
211-
],
212-
check=False,
213-
capture_output=True,
214-
text=True,
215-
timeout=20,
223+
]
216224
)
225+
out = capsys.readouterr().out
217226

218-
assert p.returncode == 0
219-
assert "QPE completed" in p.stdout
220-
assert "system=4, ancillas=1" in p.stdout
227+
assert captured["molecule"] == "H2"
228+
assert captured["symbols"] == ["H", "H"]
229+
assert np.array_equal(
230+
captured["coordinates"],
231+
np.array([[0.0, 0.0, 0.0], [0.0, 0.0, 0.7]], dtype=float),
232+
)
233+
assert captured["charge"] == 0
234+
assert captured["basis"] == "sto-3g"
235+
assert captured["n_ancilla"] == 1
236+
assert captured["shots"] == 50
237+
assert captured["force"] is True
238+
assert "QPE completed" in out
239+
assert "system=4, ancillas=1" in out
221240

222241

242+
@pytest.mark.slow
243+
@pytest.mark.cli_subprocess
223244
def test_qpe_cli_returns_nonzero_on_failure() -> None:
224245
p = subprocess.run(
225246
[sys.executable, "-m", "qpe", "--molecule", "DOES_NOT_EXIST"],

vqe/__main__.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -586,7 +586,7 @@ def handle_special_modes(args) -> bool:
586586
# ================================================================
587587
# MAIN ENTRYPOINT
588588
# ================================================================
589-
def main() -> None:
589+
def build_parser() -> argparse.ArgumentParser:
590590
parser = argparse.ArgumentParser(
591591
prog="vqe",
592592
description="VQE / SSVQE / VQD Simulation Toolkit",
@@ -951,7 +951,12 @@ def main() -> None:
951951
misc.add_argument("--force", action="store_true", help="Ignore cached results")
952952
misc.add_argument("--plot", action="store_true", help="Plot convergence")
953953

954-
args = parser.parse_args()
954+
return parser
955+
956+
957+
def main(argv: list[str] | None = None) -> None:
958+
parser = build_parser()
959+
args = parser.parse_args(argv)
955960
symbols, coordinates = _validated_geometry_inputs(args)
956961
explicit_geometry = symbols is not None and coordinates is not None
957962

0 commit comments

Comments
 (0)