pintest-cli 0.2.6__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.
Files changed (28) hide show
  1. {pintest_cli-0.2.6 → pintest_cli-0.2.7}/PKG-INFO +1 -1
  2. {pintest_cli-0.2.6 → pintest_cli-0.2.7}/pintest/build_mapping_iterative.py +1 -0
  3. {pintest_cli-0.2.6 → pintest_cli-0.2.7}/pintest/cli.py +1 -1
  4. {pintest_cli-0.2.6 → pintest_cli-0.2.7}/pintest/cloud_mapping_db.py +11 -1
  5. {pintest_cli-0.2.6 → pintest_cli-0.2.7}/pintest/pre_commit_hook.py +3 -0
  6. {pintest_cli-0.2.6 → pintest_cli-0.2.7}/pintest/push_cache.py +15 -9
  7. pintest_cli-0.2.7/pintest/pytest_plugin.py +24 -0
  8. {pintest_cli-0.2.6 → pintest_cli-0.2.7}/pintest_cli.egg-info/PKG-INFO +1 -1
  9. {pintest_cli-0.2.6 → pintest_cli-0.2.7}/pintest_cli.egg-info/SOURCES.txt +1 -0
  10. {pintest_cli-0.2.6 → pintest_cli-0.2.7}/setup.py +1 -1
  11. {pintest_cli-0.2.6 → pintest_cli-0.2.7}/README.md +0 -0
  12. {pintest_cli-0.2.6 → pintest_cli-0.2.7}/pintest/__init__.py +0 -0
  13. {pintest_cli-0.2.6 → pintest_cli-0.2.7}/pintest/config.py +0 -0
  14. {pintest_cli-0.2.6 → pintest_cli-0.2.7}/pintest/coverage_mapper.py +0 -0
  15. {pintest_cli-0.2.6 → pintest_cli-0.2.7}/pintest/git_diff_parser.py +0 -0
  16. {pintest_cli-0.2.6 → pintest_cli-0.2.7}/pintest/post_commit_hook.py +0 -0
  17. {pintest_cli-0.2.6 → pintest_cli-0.2.7}/pintest/range_set.py +0 -0
  18. {pintest_cli-0.2.6 → pintest_cli-0.2.7}/pintest/test_mapping_db_v2.py +0 -0
  19. {pintest_cli-0.2.6 → pintest_cli-0.2.7}/pintest/update_mapping.py +0 -0
  20. {pintest_cli-0.2.6 → pintest_cli-0.2.7}/pintest_cli.egg-info/dependency_links.txt +0 -0
  21. {pintest_cli-0.2.6 → pintest_cli-0.2.7}/pintest_cli.egg-info/entry_points.txt +0 -0
  22. {pintest_cli-0.2.6 → pintest_cli-0.2.7}/pintest_cli.egg-info/requires.txt +0 -0
  23. {pintest_cli-0.2.6 → pintest_cli-0.2.7}/pintest_cli.egg-info/top_level.txt +0 -0
  24. {pintest_cli-0.2.6 → pintest_cli-0.2.7}/setup.cfg +0 -0
  25. {pintest_cli-0.2.6 → pintest_cli-0.2.7}/tests/__init__.py +0 -0
  26. {pintest_cli-0.2.6 → pintest_cli-0.2.7}/tests/test_git_diff_parser.py +0 -0
  27. {pintest_cli-0.2.6 → pintest_cli-0.2.7}/tests/test_new_feature.py +0 -0
  28. {pintest_cli-0.2.6 → pintest_cli-0.2.7}/tests/test_range_set.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pintest-cli
3
- Version: 0.2.6
3
+ Version: 0.2.7
4
4
  Summary: Run only the tests affected by your code changes.
5
5
  Author: Pintest Contributors
6
6
  Classifier: Development Status :: 3 - Alpha
@@ -52,6 +52,7 @@ def run_test_chunk_with_mapping(
52
52
  # Run chunk of tests with coverage
53
53
  cmd = [
54
54
  sys.executable, "-m", "pytest",
55
+ "-p", "pintest.pytest_plugin",
55
56
  "--cov",
56
57
  "--cov-context=test",
57
58
  "--cov-append",
@@ -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="])
@@ -142,15 +142,25 @@ class CloudMappingDB:
142
142
  push_cache = PushCache(cache_db_path)
143
143
  cached_state = push_cache.get_cached_state(self._branch)
144
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
+
145
152
  mappings = []
146
153
  for (test_name, file_path), rs in test_file_ranges.items():
147
154
  compact_ranges = rs.to_compact_string()
148
155
  key = (test_name, file_path)
149
- if key not in cached_state or cached_state[key] != compact_ranges:
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):
150
159
  mappings.append({
151
160
  "test_name": test_name,
152
161
  "file_path": file_path,
153
162
  "ranges": compact_ranges,
163
+ "duration_ms": duration_ms,
154
164
  })
155
165
 
156
166
  if not mappings:
@@ -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
@@ -23,39 +23,45 @@ class PushCache:
23
23
  test_name TEXT,
24
24
  file_path TEXT,
25
25
  ranges TEXT,
26
+ duration_ms INTEGER DEFAULT 0,
26
27
  last_pushed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
27
28
  PRIMARY KEY (branch, test_name, file_path)
28
29
  )
29
30
  """)
31
+ try:
32
+ conn.execute("ALTER TABLE push_cache ADD COLUMN duration_ms INTEGER DEFAULT 0")
33
+ except sqlite3.OperationalError:
34
+ pass
30
35
  conn.execute("CREATE INDEX IF NOT EXISTS idx_push_cache_lookup ON push_cache(branch)")
31
36
 
32
- def get_cached_state(self, branch: str) -> Dict[Tuple[str, str], str]:
37
+ def get_cached_state(self, branch: str) -> Dict[Tuple[str, str], Tuple[str, int]]:
33
38
  """
34
39
  Fetch the last successfully pushed state for the active branch.
35
40
 
36
41
  Returns:
37
- {(test_name, file_path): ranges_string}
42
+ {(test_name, file_path): (ranges_string, duration_ms)}
38
43
  """
39
44
  with sqlite3.connect(self.db_path) as conn:
40
45
  cursor = conn.execute(
41
- "SELECT test_name, file_path, ranges FROM push_cache WHERE branch = ?",
46
+ "SELECT test_name, file_path, ranges, duration_ms FROM push_cache WHERE branch = ?",
42
47
  (branch,)
43
48
  )
44
- return {(row[0], row[1]): row[2] for row in cursor}
49
+ return {(row[0], row[1]): (row[2], row[3] if row[3] is not None else 0) for row in cursor}
45
50
 
46
- def batch_upsert(self, branch: str, mappings: List[Dict[str, str]]):
51
+ def batch_upsert(self, branch: str, mappings: List[Dict]):
47
52
  """
48
53
  Atomically update the cache with successfully pushed mappings.
49
54
 
50
55
  Args:
51
56
  branch: Active git branch
52
- mappings: List of dicts [{"test_name": ..., "file_path": ..., "ranges": ...}]
57
+ mappings: List of dicts [{"test_name": ..., "file_path": ..., "ranges": ..., "duration_ms": ...}]
53
58
  """
54
59
  with sqlite3.connect(self.db_path) as conn:
55
60
  conn.executemany("""
56
- INSERT INTO push_cache (branch, test_name, file_path, ranges, last_pushed_at)
57
- VALUES (?, ?, ?, ?, CURRENT_TIMESTAMP)
61
+ INSERT INTO push_cache (branch, test_name, file_path, ranges, duration_ms, last_pushed_at)
62
+ VALUES (?, ?, ?, ?, ?, CURRENT_TIMESTAMP)
58
63
  ON CONFLICT(branch, test_name, file_path) DO UPDATE SET
59
64
  ranges = EXCLUDED.ranges,
65
+ duration_ms = CASE WHEN EXCLUDED.duration_ms > 0 THEN EXCLUDED.duration_ms ELSE push_cache.duration_ms END,
60
66
  last_pushed_at = CURRENT_TIMESTAMP
61
- """, [(branch, m["test_name"], m["file_path"], m["ranges"]) for m in mappings])
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
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pintest-cli
3
- Version: 0.2.6
3
+ Version: 0.2.7
4
4
  Summary: Run only the tests affected by your code changes.
5
5
  Author: Pintest Contributors
6
6
  Classifier: Development Status :: 3 - Alpha
@@ -10,6 +10,7 @@ pintest/git_diff_parser.py
10
10
  pintest/post_commit_hook.py
11
11
  pintest/pre_commit_hook.py
12
12
  pintest/push_cache.py
13
+ pintest/pytest_plugin.py
13
14
  pintest/range_set.py
14
15
  pintest/test_mapping_db_v2.py
15
16
  pintest/update_mapping.py
@@ -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.6",
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