Skip to content

Commit e3e6e0a

Browse files
feat(build): configuration management system
changes: - file: conftest.py area: test added: [sample_files, disk_sources, sample_sources, default_config, sample_snapshot] stats: lines: "+280/-7 (net +273)" files: 4 complexity: "Large structural change (normalized)"
1 parent 2796b0f commit e3e6e0a

File tree

8 files changed

+294
-10
lines changed

8 files changed

+294
-10
lines changed

.github/workflows/ci.yml

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches: [main]
6+
pull_request:
7+
branches: [main]
8+
9+
env:
10+
PIP_CACHE_DIR: ~/.cache/pip
11+
PIP_DISABLE_PIP_VERSION_CHECK: "1"
12+
13+
jobs:
14+
test:
15+
runs-on: ubuntu-latest
16+
strategy:
17+
matrix:
18+
python-version: ["3.11", "3.12", "3.13"]
19+
20+
steps:
21+
- uses: actions/checkout@v4
22+
23+
- name: Set up Python ${{ matrix.python-version }}
24+
uses: actions/setup-python@v5
25+
with:
26+
python-version: ${{ matrix.python-version }}
27+
28+
# ── Cache pip downloads ────────────────────────────────────
29+
- name: Cache pip packages
30+
uses: actions/cache@v4
31+
with:
32+
path: |
33+
~/.cache/pip
34+
.venv
35+
key: pip-${{ runner.os }}-py${{ matrix.python-version }}-${{ hashFiles('pyproject.toml') }}
36+
restore-keys: |
37+
pip-${{ runner.os }}-py${{ matrix.python-version }}-
38+
39+
# ── Install deps (uses cache if available) ─────────────────
40+
- name: Install dependencies
41+
run: |
42+
python -m venv .venv
43+
source .venv/bin/activate
44+
pip install -e ".[dev]"
45+
46+
# ── Tests (offline — all deps already cached) ──────────────
47+
- name: Run tests
48+
run: |
49+
source .venv/bin/activate
50+
python -m pytest tests/ -q --tb=short
51+
52+
# ── Benchmark (plain output, fail on threshold) ────────────
53+
- name: Performance benchmark
54+
run: |
55+
source .venv/bin/activate
56+
python -m regix.benchmark --plain --threshold 30.0
57+
58+
benchmark:
59+
runs-on: ubuntu-latest
60+
needs: test
61+
if: github.ref == 'refs/heads/main'
62+
63+
steps:
64+
- uses: actions/checkout@v4
65+
66+
- uses: actions/setup-python@v5
67+
with:
68+
python-version: "3.13"
69+
70+
- name: Cache pip packages
71+
uses: actions/cache@v4
72+
with:
73+
path: |
74+
~/.cache/pip
75+
.venv
76+
key: pip-${{ runner.os }}-py3.13-${{ hashFiles('pyproject.toml') }}
77+
restore-keys: |
78+
pip-${{ runner.os }}-py3.13-
79+
80+
- name: Install dependencies
81+
run: |
82+
python -m venv .venv
83+
source .venv/bin/activate
84+
pip install -e ".[dev]"
85+
86+
- name: Full benchmark (JSON artifact)
87+
run: |
88+
source .venv/bin/activate
89+
python -m regix.benchmark --json > benchmark.json
90+
91+
- name: Upload benchmark results
92+
uses: actions/upload-artifact@v4
93+
with:
94+
name: benchmark-results
95+
path: benchmark.json
96+
retention-days: 30

CHANGELOG.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
5252

5353
## [Unreleased]
5454

55+
## [0.1.6] - 2026-03-31
56+
57+
### Docs
58+
- Update README.md
59+
60+
### Test
61+
- Update tests/conftest.py
62+
63+
### Other
64+
- Update Makefile
65+
5566
## [0.1.5] - 2026-03-31
5667

5768
### Docs

Makefile

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ FLAKE8 = python3 -m flake8
1818
COVERAGE = python3 -m coverage
1919
PART = patch
2020

21-
.PHONY: help install dev test build publish clean push benchmark benchmark-ci benchmark-json
21+
.PHONY: help install dev test build publish clean push benchmark benchmark-ci benchmark-json deps-cache deps-lock test-offline
2222

2323
help:
2424
@echo "Targets:"
@@ -29,9 +29,12 @@ help:
2929
@echo " make publish - build and upload to PyPI"
3030
@echo " make clean - remove build artifacts"
3131
@echo " make push - use goal to push changes"
32-
@echo " make benchmark - run performance benchmark"
33-
@echo " make benchmark-ci - benchmark for CI (plain, threshold)"
32+
@echo " make benchmark - run performance benchmark"
33+
@echo " make benchmark-ci - benchmark for CI (plain, threshold)"
3434
@echo " make benchmark-json - benchmark JSON output"
35+
@echo " make deps-cache - download all deps to local wheel cache"
36+
@echo " make deps-lock - freeze current versions to requirements-lock.txt"
37+
@echo " make test-offline - run tests without network (uses cached deps)"
3538

3639
install:
3740
pip install .
@@ -74,6 +77,36 @@ docker-matrix:
7477
bash integration/run_docker_matrix.sh
7578

7679

80+
# ── Dependency caching ──────────────────────────────────────────────────────
81+
82+
WHEEL_CACHE := .wheels
83+
84+
deps-cache:
85+
@echo "${YELLOW}Downloading all deps to local wheel cache...${RESET}"
86+
mkdir -p $(WHEEL_CACHE)
87+
$(PIP) download -d $(WHEEL_CACHE) -e ".[dev]"
88+
@echo "${GREEN}Cached $$(ls $(WHEEL_CACHE) | wc -l) wheels in $(WHEEL_CACHE)/${RESET}"
89+
90+
deps-lock:
91+
@echo "${YELLOW}Freezing current versions...${RESET}"
92+
$(PIP) freeze --exclude-editable > requirements-lock.txt
93+
@echo "${GREEN}Wrote requirements-lock.txt${RESET}"
94+
95+
test-offline:
96+
@echo "${YELLOW}Running tests offline (no network)...${RESET}"
97+
$(PYTHON) -m pytest tests/ -q --tb=short -p no:cacheprovider
98+
@echo "${GREEN}Offline tests complete${RESET}"
99+
100+
# ── Install from local cache (no internet) ──────────────────────────────────
101+
102+
install-offline:
103+
@if [ ! -d "$(WHEEL_CACHE)" ]; then \
104+
echo "${YELLOW}No wheel cache found. Run 'make deps-cache' first.${RESET}"; \
105+
exit 1; \
106+
fi
107+
$(PIP) install --no-index --find-links=$(WHEEL_CACHE) -e ".[dev]"
108+
109+
77110
## Bump version (e.g., make bump-version PART=patch)
78111
bump-version:
79112
@if [ -z "$(PART)" ]; then \

README.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,11 @@
33

44
## AI Cost Tracking
55

6-
![PyPI](https://img.shields.io/badge/pypi-costs-blue) ![Version](https://img.shields.io/badge/version-0.1.5-blue) ![Python](https://img.shields.io/badge/python-3.9+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
7-
![AI Cost](https://img.shields.io/badge/AI%20Cost-$0.75-orange) ![Human Time](https://img.shields.io/badge/Human%20Time-2.1h-blue) ![Model](https://img.shields.io/badge/Model-openrouter%2Fqwen%2Fqwen3--coder--next-lightgrey)
6+
![PyPI](https://img.shields.io/badge/pypi-costs-blue) ![Version](https://img.shields.io/badge/version-0.1.6-blue) ![Python](https://img.shields.io/badge/python-3.9+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
7+
![AI Cost](https://img.shields.io/badge/AI%20Cost-$0.90-orange) ![Human Time](https://img.shields.io/badge/Human%20Time-2.9h-blue) ![Model](https://img.shields.io/badge/Model-openrouter%2Fqwen%2Fqwen3--coder--next-lightgrey)
88

9-
- 🤖 **LLM usage:** $0.7500 (5 commits)
10-
- 👤 **Human dev:** ~$211 (2.1h @ $100/h, 30min dedup)
9+
- 🤖 **LLM usage:** $0.9000 (6 commits)
10+
- 👤 **Human dev:** ~$294 (2.9h @ $100/h, 30min dedup)
1111

1212
Generated on 2026-03-31 using [openrouter/qwen/qwen3-coder-next](https://openrouter.ai/qwen/qwen3-coder-next)
1313

VERSION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
0.1.5
1+
0.1.6

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
44

55
[project]
66
name = "regix"
7-
version = "0.1.5"
7+
version = "0.1.6"
88
description = "Regression Index — detect and measure code quality regressions between git versions"
99
readme = "README.md"
1010
license = "Apache-2.0"

regix/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,4 +133,4 @@ def check_gates(self, ref: str = "HEAD") -> GateResult:
133133
"ArchSmell",
134134
]
135135

136-
__version__ = "0.1.5"
136+
__version__ = "0.1.6"

tests/conftest.py

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
"""Shared test fixtures — in-memory sources, pre-built configs and snapshots.
2+
3+
All fixtures use RAM-only data so tests never hit the network or
4+
create temporary files on disk (unless explicitly needed via tmp_path).
5+
"""
6+
7+
from __future__ import annotations
8+
9+
from datetime import datetime, timezone
10+
from pathlib import Path
11+
12+
import pytest
13+
14+
from regix.config import RegressionConfig
15+
from regix.models import Snapshot, SymbolMetrics
16+
17+
18+
# ── Reusable source code snippets ──────────────────────────────────────────
19+
20+
SAMPLE_SOURCE_SIMPLE = '''\
21+
import os
22+
23+
def greet(name):
24+
"""Say hello."""
25+
return f"Hello, {name}!"
26+
27+
class Greeter:
28+
"""A greeter class."""
29+
def __init__(self, prefix="Hi"):
30+
self.prefix = prefix
31+
32+
def say(self, name):
33+
"""Say greeting."""
34+
return f"{self.prefix}, {name}"
35+
'''
36+
37+
SAMPLE_SOURCE_COMPLEX = '''\
38+
import os
39+
import sys
40+
from pathlib import Path
41+
42+
43+
def compute(data, factor=1):
44+
"""Process data with nested logic."""
45+
result = []
46+
for item in data:
47+
if item > 0:
48+
for sub in range(item):
49+
if sub % 2 == 0:
50+
result.append(sub * factor)
51+
else:
52+
result.append(sub + factor)
53+
else:
54+
result.append(0)
55+
return result
56+
57+
58+
def transform(items):
59+
total = 0
60+
for item in items:
61+
total += item
62+
return total / len(items) if items else 0
63+
64+
65+
class Handler:
66+
def __init__(self, config):
67+
self.config = config
68+
self._cache = {}
69+
70+
def process(self, value):
71+
if value in self._cache:
72+
return self._cache[value]
73+
result = self._compute(value)
74+
self._cache[value] = result
75+
return result
76+
77+
def _compute(self, value):
78+
return value * 2 + 1
79+
'''
80+
81+
SAMPLE_SOURCE_NO_DOCSTRINGS = '''\
82+
def undocumented_a():
83+
pass
84+
85+
def undocumented_b(x):
86+
return x + 1
87+
88+
class Bare:
89+
def method(self):
90+
pass
91+
'''
92+
93+
94+
# ── Fixtures ───────────────────────────────────────────────────────────────
95+
96+
@pytest.fixture
97+
def default_config() -> RegressionConfig:
98+
"""Pre-built default config — no file I/O."""
99+
return RegressionConfig()
100+
101+
102+
@pytest.fixture
103+
def sample_sources() -> dict[str, str]:
104+
"""In-memory source dict with 3 synthetic files."""
105+
return {
106+
"simple.py": SAMPLE_SOURCE_SIMPLE,
107+
"complex.py": SAMPLE_SOURCE_COMPLEX,
108+
"bare.py": SAMPLE_SOURCE_NO_DOCSTRINGS,
109+
}
110+
111+
112+
@pytest.fixture
113+
def sample_files(sample_sources: dict[str, str]) -> list[Path]:
114+
"""File list matching sample_sources keys."""
115+
return [Path(k) for k in sample_sources]
116+
117+
118+
@pytest.fixture
119+
def sample_snapshot(sample_sources: dict[str, str]) -> Snapshot:
120+
"""A Snapshot built entirely in RAM from sample sources."""
121+
symbols = []
122+
for fname in sample_sources:
123+
symbols.append(SymbolMetrics(file=fname, symbol=None, cc=5, mi=50.0))
124+
return Snapshot(
125+
ref="HEAD",
126+
commit_sha="abc1234",
127+
timestamp=datetime.now(timezone.utc),
128+
workdir=".",
129+
symbols=symbols,
130+
)
131+
132+
133+
@pytest.fixture
134+
def disk_sources(tmp_path: Path, sample_sources: dict[str, str]) -> tuple[Path, list[Path]]:
135+
"""Write sample sources to tmp_path for backends that need disk.
136+
137+
Returns (workdir, file_list).
138+
"""
139+
files: list[Path] = []
140+
for name, content in sample_sources.items():
141+
p = tmp_path / name
142+
p.write_text(content, encoding="utf-8")
143+
files.append(Path(name))
144+
return tmp_path, files

0 commit comments

Comments
 (0)