Skip to content

Commit 9ec532c

Browse files
Copilotshaypal5
andcommitted
Add smoke test suite and fast-fail CI gates
Co-authored-by: shaypal5 <917954+shaypal5@users.noreply.github.com>
1 parent 461180d commit 9ec532c

File tree

3 files changed

+215
-1
lines changed

3 files changed

+215
-1
lines changed

.github/workflows/ci-test.yml

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,51 @@ concurrency:
1515
cancel-in-progress: ${{ github.ref != 'refs/heads/master' }}
1616

1717
jobs:
18+
smoke-test:
19+
runs-on: ubuntu-latest
20+
permissions:
21+
contents: read
22+
if: github.event_name == 'push' || github.event_name == 'pull_request'
23+
steps:
24+
- uses: actions/checkout@v6
25+
26+
- name: Set up Python
27+
uses: actions/setup-python@v6
28+
with:
29+
python-version: "3.12"
30+
cache: "pip"
31+
32+
- name: Install package & dependencies
33+
run: python -m pip install -e . -r tests/requirements.txt
34+
35+
- name: Run smoke tests
36+
run: pytest -m smoke tests/test_smoke.py
37+
38+
pre-commit:
39+
runs-on: ubuntu-latest
40+
permissions:
41+
contents: read
42+
if: github.event_name == 'push' || github.event_name == 'pull_request'
43+
steps:
44+
- uses: actions/checkout@v6
45+
46+
- name: Set up Python
47+
uses: actions/setup-python@v6
48+
with:
49+
python-version: "3.12"
50+
cache: "pip"
51+
52+
- name: Install pre-commit
53+
run: pip install pre-commit
54+
55+
- name: Run pre-commit
56+
run: pre-commit run --all-files
57+
1858
pytester:
1959
# run on both push & normal PR
60+
needs: [smoke-test, pre-commit]
61+
permissions:
62+
contents: read
2063
if: github.event_name == 'push' || github.event_name == 'pull_request'
2164
runs-on: ${{ matrix.os }}
2265
environment: test
@@ -146,7 +189,7 @@ jobs:
146189

147190
testing-guardian:
148191
runs-on: ubuntu-latest
149-
needs: pytester
192+
needs: [smoke-test, pre-commit, pytester]
150193
if: always()
151194
steps:
152195
- run: echo "${{ needs.pytester.result }}"

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,7 @@ markers = [
178178
"sql: test the SQL core",
179179
"maxage: test the max_age functionality",
180180
"asyncio: marks tests as async",
181+
"smoke: fast smoke tests with no external service dependencies",
181182
]
182183

183184
[tool.coverage.report]

tests/test_smoke.py

Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
"""Smoke tests for cachier - fast, no external service dependencies."""
2+
3+
import datetime
4+
5+
import pytest
6+
7+
import cachier
8+
from cachier import cachier as cachier_decorator
9+
from cachier.config import get_global_params, set_global_params
10+
11+
12+
@pytest.mark.smoke
13+
def test_import():
14+
"""Test that cachier can be imported and has a version."""
15+
assert cachier.__version__
16+
assert isinstance(cachier.__version__, str)
17+
18+
19+
@pytest.mark.smoke
20+
def test_get_global_params():
21+
"""Test that global params can be retrieved."""
22+
params = get_global_params()
23+
assert params is not None
24+
assert hasattr(params, "backend")
25+
assert hasattr(params, "stale_after")
26+
27+
28+
@pytest.mark.smoke
29+
def test_invalid_backend():
30+
"""Test that an invalid backend raises a ValueError."""
31+
with pytest.raises(ValueError, match="specified an invalid core"):
32+
33+
@cachier_decorator(backend="invalid_backend")
34+
def dummy():
35+
pass
36+
37+
38+
@pytest.mark.smoke
39+
def test_pickle_backend_basic(tmp_path):
40+
"""Test basic caching with the pickle backend."""
41+
call_count = 0
42+
43+
@cachier_decorator(cache_dir=tmp_path, backend="pickle")
44+
def add(a, b):
45+
nonlocal call_count
46+
call_count += 1
47+
return a + b
48+
49+
add.clear_cache()
50+
assert call_count == 0
51+
assert add(1, 2) == 3
52+
assert call_count == 1
53+
assert add(1, 2) == 3
54+
assert call_count == 1 # cache hit
55+
add.clear_cache()
56+
57+
58+
@pytest.mark.smoke
59+
def test_memory_backend_basic():
60+
"""Test basic caching with the memory backend."""
61+
call_count = 0
62+
63+
@cachier_decorator(backend="memory")
64+
def multiply(a, b):
65+
nonlocal call_count
66+
call_count += 1
67+
return a * b
68+
69+
multiply.clear_cache()
70+
assert call_count == 0
71+
assert multiply(3, 4) == 12
72+
assert call_count == 1
73+
assert multiply(3, 4) == 12
74+
assert call_count == 1 # cache hit
75+
multiply.clear_cache()
76+
77+
78+
@pytest.mark.smoke
79+
def test_clear_cache(tmp_path):
80+
"""Test that clear_cache resets the cache."""
81+
call_count = 0
82+
83+
@cachier_decorator(cache_dir=tmp_path, backend="pickle")
84+
def func():
85+
nonlocal call_count
86+
call_count += 1
87+
return 42
88+
89+
func.clear_cache()
90+
func()
91+
func()
92+
assert call_count == 1
93+
func.clear_cache()
94+
func()
95+
assert call_count == 2
96+
func.clear_cache()
97+
98+
99+
@pytest.mark.smoke
100+
def test_pickle_backend_stale_after(tmp_path):
101+
"""Test that stale_after=timedelta(0) always recalculates."""
102+
call_count = 0
103+
104+
@cachier_decorator(
105+
cache_dir=tmp_path,
106+
backend="pickle",
107+
stale_after=datetime.timedelta(seconds=0),
108+
next_time=False,
109+
)
110+
def func(x):
111+
nonlocal call_count
112+
call_count += 1
113+
return x * 2
114+
115+
func.clear_cache()
116+
assert func(5) == 10
117+
assert func(5) == 10
118+
assert call_count == 2 # always recalculates when immediately stale
119+
func.clear_cache()
120+
121+
122+
@pytest.mark.smoke
123+
def test_allow_none(tmp_path):
124+
"""Test that allow_none=True caches None return values."""
125+
call_count = 0
126+
127+
@cachier_decorator(cache_dir=tmp_path, backend="pickle", allow_none=True)
128+
def returns_none():
129+
nonlocal call_count
130+
call_count += 1
131+
return None
132+
133+
returns_none.clear_cache()
134+
assert returns_none() is None
135+
assert returns_none() is None
136+
assert call_count == 1 # second call uses cache
137+
returns_none.clear_cache()
138+
139+
140+
@pytest.mark.smoke
141+
def test_set_global_params_backend():
142+
"""Test that set_global_params changes the active backend."""
143+
original = get_global_params().backend
144+
try:
145+
set_global_params(backend="memory")
146+
assert get_global_params().backend == "memory"
147+
finally:
148+
set_global_params(backend=original)
149+
150+
151+
@pytest.mark.smoke
152+
def test_cache_dpath_pickle(tmp_path):
153+
"""Test that cache_dpath returns a path for the pickle backend."""
154+
155+
@cachier_decorator(cache_dir=tmp_path, backend="pickle")
156+
def func():
157+
return 1
158+
159+
assert func.cache_dpath() is not None
160+
161+
162+
@pytest.mark.smoke
163+
def test_cache_dpath_memory():
164+
"""Test that cache_dpath returns None for the memory backend."""
165+
166+
@cachier_decorator(backend="memory")
167+
def func():
168+
return 1
169+
170+
assert func.cache_dpath() is None

0 commit comments

Comments
 (0)