pintest-cli 0.2.5__tar.gz → 0.2.7__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {pintest_cli-0.2.5 → pintest_cli-0.2.7}/PKG-INFO +1 -1
- {pintest_cli-0.2.5 → pintest_cli-0.2.7}/pintest/build_mapping_iterative.py +1 -0
- {pintest_cli-0.2.5 → pintest_cli-0.2.7}/pintest/cli.py +1 -1
- {pintest_cli-0.2.5 → pintest_cli-0.2.7}/pintest/cloud_mapping_db.py +26 -7
- {pintest_cli-0.2.5 → pintest_cli-0.2.7}/pintest/pre_commit_hook.py +3 -0
- pintest_cli-0.2.7/pintest/push_cache.py +67 -0
- pintest_cli-0.2.7/pintest/pytest_plugin.py +24 -0
- {pintest_cli-0.2.5 → pintest_cli-0.2.7}/pintest_cli.egg-info/PKG-INFO +1 -1
- {pintest_cli-0.2.5 → pintest_cli-0.2.7}/pintest_cli.egg-info/SOURCES.txt +2 -0
- {pintest_cli-0.2.5 → pintest_cli-0.2.7}/setup.py +1 -1
- {pintest_cli-0.2.5 → pintest_cli-0.2.7}/README.md +0 -0
- {pintest_cli-0.2.5 → pintest_cli-0.2.7}/pintest/__init__.py +0 -0
- {pintest_cli-0.2.5 → pintest_cli-0.2.7}/pintest/config.py +0 -0
- {pintest_cli-0.2.5 → pintest_cli-0.2.7}/pintest/coverage_mapper.py +0 -0
- {pintest_cli-0.2.5 → pintest_cli-0.2.7}/pintest/git_diff_parser.py +0 -0
- {pintest_cli-0.2.5 → pintest_cli-0.2.7}/pintest/post_commit_hook.py +0 -0
- {pintest_cli-0.2.5 → pintest_cli-0.2.7}/pintest/range_set.py +0 -0
- {pintest_cli-0.2.5 → pintest_cli-0.2.7}/pintest/test_mapping_db_v2.py +0 -0
- {pintest_cli-0.2.5 → pintest_cli-0.2.7}/pintest/update_mapping.py +0 -0
- {pintest_cli-0.2.5 → pintest_cli-0.2.7}/pintest_cli.egg-info/dependency_links.txt +0 -0
- {pintest_cli-0.2.5 → pintest_cli-0.2.7}/pintest_cli.egg-info/entry_points.txt +0 -0
- {pintest_cli-0.2.5 → pintest_cli-0.2.7}/pintest_cli.egg-info/requires.txt +0 -0
- {pintest_cli-0.2.5 → pintest_cli-0.2.7}/pintest_cli.egg-info/top_level.txt +0 -0
- {pintest_cli-0.2.5 → pintest_cli-0.2.7}/setup.cfg +0 -0
- {pintest_cli-0.2.5 → pintest_cli-0.2.7}/tests/__init__.py +0 -0
- {pintest_cli-0.2.5 → pintest_cli-0.2.7}/tests/test_git_diff_parser.py +0 -0
- {pintest_cli-0.2.5 → pintest_cli-0.2.7}/tests/test_new_feature.py +0 -0
- {pintest_cli-0.2.5 → pintest_cli-0.2.7}/tests/test_range_set.py +0 -0
|
@@ -138,7 +138,7 @@ class PintestRunner:
|
|
|
138
138
|
return 0
|
|
139
139
|
|
|
140
140
|
# Build pytest command
|
|
141
|
-
cmd = [sys.executable, "-m", "pytest"]
|
|
141
|
+
cmd = [sys.executable, "-m", "pytest", "-p", "pintest.pytest_plugin"]
|
|
142
142
|
|
|
143
143
|
# Automatically generate coverage for local updates (suppress terminal report)
|
|
144
144
|
cmd.extend(["--cov", "--cov-context=test", "--cov-append", "--cov-report="])
|
|
@@ -108,6 +108,7 @@ class CloudMappingDB:
|
|
|
108
108
|
True on success, False on failure
|
|
109
109
|
"""
|
|
110
110
|
from .coverage_mapper import CoverageMapper # existing module
|
|
111
|
+
from .push_cache import PushCache
|
|
111
112
|
|
|
112
113
|
if not coverage_file.exists():
|
|
113
114
|
if verbose:
|
|
@@ -137,19 +138,36 @@ class CloudMappingDB:
|
|
|
137
138
|
test_file_ranges[key] = RangeSet()
|
|
138
139
|
test_file_ranges[key].add_range(line_num, line_num)
|
|
139
140
|
|
|
141
|
+
cache_db_path = coverage_file.parent / ".pintest_push_cache.db"
|
|
142
|
+
push_cache = PushCache(cache_db_path)
|
|
143
|
+
cached_state = push_cache.get_cached_state(self._branch)
|
|
144
|
+
|
|
145
|
+
import json
|
|
146
|
+
durations_file = coverage_file.parent / ".pintest_durations.json"
|
|
147
|
+
try:
|
|
148
|
+
test_durations = json.loads(durations_file.read_text()) if durations_file.exists() else {}
|
|
149
|
+
except Exception:
|
|
150
|
+
test_durations = {}
|
|
151
|
+
|
|
140
152
|
mappings = []
|
|
141
153
|
for (test_name, file_path), rs in test_file_ranges.items():
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
154
|
+
compact_ranges = rs.to_compact_string()
|
|
155
|
+
key = (test_name, file_path)
|
|
156
|
+
duration_ms = test_durations.get(test_name, 0)
|
|
157
|
+
cached_ranges, cached_duration = cached_state.get(key, ("", 0))
|
|
158
|
+
if cached_ranges != compact_ranges or (duration_ms > 0 and duration_ms != cached_duration):
|
|
159
|
+
mappings.append({
|
|
160
|
+
"test_name": test_name,
|
|
161
|
+
"file_path": file_path,
|
|
162
|
+
"ranges": compact_ranges,
|
|
163
|
+
"duration_ms": duration_ms,
|
|
164
|
+
})
|
|
147
165
|
|
|
148
166
|
if not mappings:
|
|
149
|
-
print("ℹ️
|
|
167
|
+
print("ℹ️ All coverage mappings are up-to-date with Pintest Cloud (0 deltas)")
|
|
150
168
|
return True
|
|
151
169
|
|
|
152
|
-
print(f"☁️ Pushing {len(mappings)} mappings to Pintest...", flush=True)
|
|
170
|
+
print(f"☁️ Pushing {len(mappings)} delta mappings to Pintest...", flush=True)
|
|
153
171
|
|
|
154
172
|
payload_base = {
|
|
155
173
|
"branch": self._branch,
|
|
@@ -179,6 +197,7 @@ class CloudMappingDB:
|
|
|
179
197
|
timeout=60,
|
|
180
198
|
)
|
|
181
199
|
resp.raise_for_status()
|
|
200
|
+
push_cache.batch_upsert(self._branch, chunk_data)
|
|
182
201
|
data = resp.json()
|
|
183
202
|
inserted = data.get('inserted', 0)
|
|
184
203
|
updated = data.get('updated', 0)
|
|
@@ -426,6 +426,7 @@ def run_test_chunk_with_mapping_update(
|
|
|
426
426
|
# Run tests with coverage - output flows directly to terminal
|
|
427
427
|
cmd = [
|
|
428
428
|
sys.executable, "-m", "pytest",
|
|
429
|
+
"-p", "pintest.pytest_plugin",
|
|
429
430
|
"--cov",
|
|
430
431
|
"--cov-context=test",
|
|
431
432
|
"--cov-append",
|
|
@@ -558,6 +559,7 @@ def run_single_test_with_mapping_update(
|
|
|
558
559
|
# Run single test with coverage
|
|
559
560
|
cmd = [
|
|
560
561
|
"python", "-m", "pytest",
|
|
562
|
+
"-p", "pintest.pytest_plugin",
|
|
561
563
|
"--cov",
|
|
562
564
|
"--cov-context=test",
|
|
563
565
|
"--cov-append",
|
|
@@ -1023,6 +1025,7 @@ def run_tests_with_coverage(
|
|
|
1023
1025
|
# Build pytest command
|
|
1024
1026
|
cmd = [
|
|
1025
1027
|
sys.executable, "-m", "pytest",
|
|
1028
|
+
"-p", "pintest.pytest_plugin",
|
|
1026
1029
|
"--cov=src",
|
|
1027
1030
|
"--cov-context=test",
|
|
1028
1031
|
"--cov-append", # Append to existing coverage
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import sqlite3
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from typing import Dict, List, Tuple
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class PushCache:
|
|
7
|
+
"""
|
|
8
|
+
Manages the local SQLite cache for Pintest Cloud delta pushes.
|
|
9
|
+
Tracks previously synchronized test coverage mappings to filter out unmodified entries.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
def __init__(self, db_path: Path):
|
|
13
|
+
self.db_path = db_path
|
|
14
|
+
self._init_db()
|
|
15
|
+
|
|
16
|
+
def _init_db(self):
|
|
17
|
+
"""Initialize the local push cache database schema."""
|
|
18
|
+
with sqlite3.connect(self.db_path) as conn:
|
|
19
|
+
conn.execute("PRAGMA journal_mode=WAL")
|
|
20
|
+
conn.execute("""
|
|
21
|
+
CREATE TABLE IF NOT EXISTS push_cache (
|
|
22
|
+
branch TEXT,
|
|
23
|
+
test_name TEXT,
|
|
24
|
+
file_path TEXT,
|
|
25
|
+
ranges TEXT,
|
|
26
|
+
duration_ms INTEGER DEFAULT 0,
|
|
27
|
+
last_pushed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
28
|
+
PRIMARY KEY (branch, test_name, file_path)
|
|
29
|
+
)
|
|
30
|
+
""")
|
|
31
|
+
try:
|
|
32
|
+
conn.execute("ALTER TABLE push_cache ADD COLUMN duration_ms INTEGER DEFAULT 0")
|
|
33
|
+
except sqlite3.OperationalError:
|
|
34
|
+
pass
|
|
35
|
+
conn.execute("CREATE INDEX IF NOT EXISTS idx_push_cache_lookup ON push_cache(branch)")
|
|
36
|
+
|
|
37
|
+
def get_cached_state(self, branch: str) -> Dict[Tuple[str, str], Tuple[str, int]]:
|
|
38
|
+
"""
|
|
39
|
+
Fetch the last successfully pushed state for the active branch.
|
|
40
|
+
|
|
41
|
+
Returns:
|
|
42
|
+
{(test_name, file_path): (ranges_string, duration_ms)}
|
|
43
|
+
"""
|
|
44
|
+
with sqlite3.connect(self.db_path) as conn:
|
|
45
|
+
cursor = conn.execute(
|
|
46
|
+
"SELECT test_name, file_path, ranges, duration_ms FROM push_cache WHERE branch = ?",
|
|
47
|
+
(branch,)
|
|
48
|
+
)
|
|
49
|
+
return {(row[0], row[1]): (row[2], row[3] if row[3] is not None else 0) for row in cursor}
|
|
50
|
+
|
|
51
|
+
def batch_upsert(self, branch: str, mappings: List[Dict]):
|
|
52
|
+
"""
|
|
53
|
+
Atomically update the cache with successfully pushed mappings.
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
branch: Active git branch
|
|
57
|
+
mappings: List of dicts [{"test_name": ..., "file_path": ..., "ranges": ..., "duration_ms": ...}]
|
|
58
|
+
"""
|
|
59
|
+
with sqlite3.connect(self.db_path) as conn:
|
|
60
|
+
conn.executemany("""
|
|
61
|
+
INSERT INTO push_cache (branch, test_name, file_path, ranges, duration_ms, last_pushed_at)
|
|
62
|
+
VALUES (?, ?, ?, ?, ?, CURRENT_TIMESTAMP)
|
|
63
|
+
ON CONFLICT(branch, test_name, file_path) DO UPDATE SET
|
|
64
|
+
ranges = EXCLUDED.ranges,
|
|
65
|
+
duration_ms = CASE WHEN EXCLUDED.duration_ms > 0 THEN EXCLUDED.duration_ms ELSE push_cache.duration_ms END,
|
|
66
|
+
last_pushed_at = CURRENT_TIMESTAMP
|
|
67
|
+
""", [(branch, m["test_name"], m["file_path"], m["ranges"], m.get("duration_ms", 0)) for m in mappings])
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"""Pytest plugin to measure individual test execution duration for Pintest."""
|
|
2
|
+
import json
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
# Stores test_node_id -> duration_ms
|
|
6
|
+
test_durations = {}
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def pytest_runtest_makereport(item, call):
|
|
10
|
+
"""Record test execution time during the 'call' phase."""
|
|
11
|
+
if call.when == "call":
|
|
12
|
+
# call.duration is a float representing exact seconds
|
|
13
|
+
test_durations[item.nodeid] = int(call.duration * 1000)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def pytest_sessionfinish(session, exitstatus):
|
|
17
|
+
"""Dump durations to a temporary file for the CLI push phase."""
|
|
18
|
+
try:
|
|
19
|
+
out_file = Path(session.config.rootdir) / ".pintest_durations.json"
|
|
20
|
+
with open(out_file, "w") as f:
|
|
21
|
+
json.dump(test_durations, f)
|
|
22
|
+
except Exception as e:
|
|
23
|
+
# Silently pass if we cannot write to rootdir
|
|
24
|
+
pass
|
|
@@ -5,7 +5,7 @@ with open("README.md", "r", encoding="utf-8") as fh:
|
|
|
5
5
|
|
|
6
6
|
setup(
|
|
7
7
|
name="pintest-cli",
|
|
8
|
-
version="0.2.
|
|
8
|
+
version="0.2.7",
|
|
9
9
|
description="Run only the tests affected by your code changes.",
|
|
10
10
|
long_description=long_description,
|
|
11
11
|
long_description_content_type="text/markdown",
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|