Skip to content

Commit 69cc154

Browse files
Configure property testing workflow
1 parent c93f0e1 commit 69cc154

File tree

8 files changed

+72
-3
lines changed

8 files changed

+72
-3
lines changed

.github/workflows/ci.yml

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,15 @@ jobs:
2121
restore-keys: |
2222
${{ runner.os }}-pip-
2323
- run: python -m pip install -U pip
24-
- run: pip install -e ".[dev]"
25-
- run: pytest -q --maxfail=1 --disable-warnings --cov=sudoku_dlx --cov-report=xml
24+
- run: python -m pip install -e ".[dev]"
25+
- name: Run tests (unit & integration only)
26+
env:
27+
HYPOTHESIS_PROFILE: ci
28+
run: pytest -q --maxfail=1 --disable-warnings --cov=sudoku_dlx --cov-report=xml -m "not prop"
2629
- name: Upload coverage to Codecov
2730
uses: codecov/codecov-action@v4
2831
with:
2932
token: ${{ secrets.CODECOV_TOKEN }} # Not required for public repos
3033
files: ./coverage.xml
3134
flags: unittests
3235
fail_ci_if_error: false
33-

.github/workflows/nightly_prop.yml

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
name: nightly-prop
2+
3+
on:
4+
schedule:
5+
- cron: "17 2 * * *"
6+
workflow_dispatch: {}
7+
8+
jobs:
9+
property-tests:
10+
runs-on: ubuntu-latest
11+
steps:
12+
- uses: actions/checkout@v4
13+
- uses: actions/setup-python@v5
14+
with:
15+
python-version: "3.12"
16+
- run: python -m pip install -U pip
17+
- run: python -m pip install -e ".[dev]"
18+
- name: Run property tests (nightly profile)
19+
env:
20+
HYPOTHESIS_PROFILE: nightly
21+
run: pytest -q -m "prop"

pytest.ini

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
[pytest]
2+
markers =
3+
prop: property-based tests (slow; run nightly)
4+
addopts = -q
5+
filterwarnings =
6+
ignore::DeprecationWarning
7+
ignore::UserWarning

tests/conftest.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
from _pytest.config import Config
99
from _pytest.config.argparsing import Parser
10+
from hypothesis import HealthCheck, Phase, settings
1011

1112
PROJECT_ROOT = Path(__file__).resolve().parents[1]
1213
SRC_PATH = PROJECT_ROOT / "src"
@@ -58,3 +59,28 @@ def pytest_configure(config: Config) -> None:
5859
"pytest-cov is not installed; coverage options are ignored.",
5960
stacklevel=2,
6061
)
62+
63+
64+
# Register lean Hypothesis profiles and select via env HYPOTHESIS_PROFILE
65+
# - "ci": very small example counts, no database, suppress flaky health checks.
66+
# - "nightly": larger runs with shrinking, no deadlines.
67+
settings.register_profile(
68+
"ci",
69+
max_examples=8,
70+
phases=(Phase.generate, Phase.shrink),
71+
deadline=None,
72+
derandomize=True,
73+
suppress_health_check=[
74+
HealthCheck.too_slow,
75+
HealthCheck.filter_too_much,
76+
HealthCheck.data_too_large,
77+
],
78+
database=None,
79+
)
80+
settings.register_profile(
81+
"nightly",
82+
max_examples=200,
83+
phases=(Phase.generate, Phase.shrink),
84+
deadline=None,
85+
)
86+
settings.load_profile(os.getenv("HYPOTHESIS_PROFILE", "default"))

tests/test_prop_canonical_simple.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
1+
import pytest
12
from hypothesis import given, settings, strategies as st
23
from sudoku_dlx import from_string, canonical_form
34

45
# Keep canonical check cheap: compare original vs rot180 isomorph
6+
7+
8+
@pytest.mark.prop
59
@settings(max_examples=10, deadline=None)
610
@given(st.lists(st.sampled_from(list(".123456789")), min_size=81, max_size=81))
711
def test_canonical_invariant_under_rot180(xs):

tests/test_prop_generate_minimal_unique.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import pytest
2+
13
from sudoku_dlx import generate, count_solutions
24

35

@@ -15,6 +17,7 @@ def _is_minimal_strict(g) -> bool:
1517
return True
1618

1719

20+
@pytest.mark.prop
1821
def test_generate_minimal_unique_fast_settings():
1922
# modest givens to keep runtime under control in CI
2023
for seed in [5, 9]:

tests/test_prop_parse.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
1+
import pytest
12
from hypothesis import given, settings, strategies as st
23
from sudoku_dlx import from_string, to_string
34

45
# Allowed characters for parser (blanks + digits)
56
chars = st.sampled_from(list(".0123456789"))
67

78

9+
@pytest.mark.prop
810
@settings(max_examples=30, deadline=None)
911
@given(st.lists(chars, min_size=81, max_size=81))
1012
def test_from_to_string_round_trip_preserves_clues(xs):
@@ -24,6 +26,7 @@ def test_from_to_string_round_trip_preserves_clues(xs):
2426
ws = st.text(alphabet=st.sampled_from(list(" \t\r\n")), min_size=0, max_size=20)
2527

2628

29+
@pytest.mark.prop
2730
@settings(max_examples=20, deadline=None)
2831
@given(st.lists(chars, min_size=81, max_size=81), ws, ws)
2932
def test_parser_ignores_whitespace(xs, pre, post):

tests/test_prop_solve_idempotent.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import pytest
2+
13
from sudoku_dlx import generate, solve
24

35

@@ -14,6 +16,7 @@ def _is_full_valid(grid):
1416
return all(s == expect for s in rows + cols + boxes)
1517

1618

19+
@pytest.mark.prop
1720
def test_solve_idempotent_and_valid_small_seedset():
1821
# keep seeds small for CI speed
1922
for seed in [1, 3, 7]:

0 commit comments

Comments
 (0)