pintest-cli 0.2.0__py3-none-any.whl
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/__init__.py +14 -0
- pintest/build_mapping_iterative.py +339 -0
- pintest/cli.py +681 -0
- pintest/cloud_mapping_db.py +218 -0
- pintest/config.py +102 -0
- pintest/coverage_mapper.py +356 -0
- pintest/git_diff_parser.py +232 -0
- pintest/post_commit_hook.py +78 -0
- pintest/pre_commit_hook.py +1472 -0
- pintest/range_set.py +173 -0
- pintest/test_mapping_db_v2.py +381 -0
- pintest/update_mapping.py +130 -0
- pintest_cli-0.2.0.dist-info/METADATA +527 -0
- pintest_cli-0.2.0.dist-info/RECORD +21 -0
- pintest_cli-0.2.0.dist-info/WHEEL +5 -0
- pintest_cli-0.2.0.dist-info/entry_points.txt +2 -0
- pintest_cli-0.2.0.dist-info/top_level.txt +2 -0
- tests/__init__.py +1 -0
- tests/test_git_diff_parser.py +60 -0
- tests/test_new_feature.py +1 -0
- tests/test_range_set.py +261 -0
tests/test_range_set.py
ADDED
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
"""Tests for RangeSet class."""
|
|
2
|
+
|
|
3
|
+
# pyrefly: ignore [missing-import]
|
|
4
|
+
import pytest
|
|
5
|
+
from pintest.range_set import RangeSet
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class TestRangeSetBasics:
|
|
9
|
+
"""Test basic RangeSet operations."""
|
|
10
|
+
|
|
11
|
+
def test_empty_rangeset(self):
|
|
12
|
+
"""Test empty range set."""
|
|
13
|
+
rs = RangeSet()
|
|
14
|
+
assert len(rs.ranges) == 0
|
|
15
|
+
assert len(rs) == 0
|
|
16
|
+
assert not rs.contains(1)
|
|
17
|
+
|
|
18
|
+
def test_single_line(self):
|
|
19
|
+
"""Test single line range."""
|
|
20
|
+
rs = RangeSet()
|
|
21
|
+
rs.add_range(5, 5)
|
|
22
|
+
assert rs.ranges == [(5, 5)]
|
|
23
|
+
assert len(rs) == 1
|
|
24
|
+
assert rs.contains(5)
|
|
25
|
+
assert not rs.contains(4)
|
|
26
|
+
assert not rs.contains(6)
|
|
27
|
+
|
|
28
|
+
def test_single_range(self):
|
|
29
|
+
"""Test single multi-line range."""
|
|
30
|
+
rs = RangeSet()
|
|
31
|
+
rs.add_range(1, 5)
|
|
32
|
+
assert rs.ranges == [(1, 5)]
|
|
33
|
+
assert len(rs) == 5
|
|
34
|
+
assert rs.contains(1)
|
|
35
|
+
assert rs.contains(3)
|
|
36
|
+
assert rs.contains(5)
|
|
37
|
+
assert not rs.contains(0)
|
|
38
|
+
assert not rs.contains(6)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class TestRangeMerging:
|
|
42
|
+
"""Test range merging logic."""
|
|
43
|
+
|
|
44
|
+
def test_adjacent_ranges_merge(self):
|
|
45
|
+
"""Test that adjacent ranges are merged."""
|
|
46
|
+
rs = RangeSet()
|
|
47
|
+
rs.add_range(1, 5)
|
|
48
|
+
rs.add_range(6, 10)
|
|
49
|
+
assert rs.ranges == [(1, 10)]
|
|
50
|
+
|
|
51
|
+
def test_overlapping_ranges_merge(self):
|
|
52
|
+
"""Test that overlapping ranges are merged."""
|
|
53
|
+
rs = RangeSet()
|
|
54
|
+
rs.add_range(1, 5)
|
|
55
|
+
rs.add_range(3, 8)
|
|
56
|
+
assert rs.ranges == [(1, 8)]
|
|
57
|
+
|
|
58
|
+
def test_gap_preserves_separate_ranges(self):
|
|
59
|
+
"""Test that ranges with gaps stay separate."""
|
|
60
|
+
rs = RangeSet()
|
|
61
|
+
rs.add_range(1, 5)
|
|
62
|
+
rs.add_range(10, 15)
|
|
63
|
+
assert rs.ranges == [(1, 5), (10, 15)]
|
|
64
|
+
assert len(rs) == 11
|
|
65
|
+
|
|
66
|
+
def test_add_lines_merges_adjacent(self):
|
|
67
|
+
"""Test adding individual lines merges adjacent ranges."""
|
|
68
|
+
rs = RangeSet()
|
|
69
|
+
rs.add_lines({1, 2, 3, 5, 6, 7, 10})
|
|
70
|
+
assert rs.ranges == [(1, 3), (5, 7), (10, 10)]
|
|
71
|
+
|
|
72
|
+
def test_multiple_merges(self):
|
|
73
|
+
"""Test multiple ranges merging together."""
|
|
74
|
+
rs = RangeSet()
|
|
75
|
+
rs.add_range(1, 3)
|
|
76
|
+
rs.add_range(10, 12)
|
|
77
|
+
rs.add_range(20, 22)
|
|
78
|
+
rs.add_range(4, 15) # This should merge first two ranges (3 and 4 are adjacent)
|
|
79
|
+
assert rs.ranges == [(1, 15), (20, 22)]
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class TestRangeRemoval:
|
|
83
|
+
"""Test range removal and splitting."""
|
|
84
|
+
|
|
85
|
+
def test_remove_middle_splits_range(self):
|
|
86
|
+
"""Test removing middle of range splits it."""
|
|
87
|
+
rs = RangeSet()
|
|
88
|
+
rs.add_range(1, 10)
|
|
89
|
+
rs.remove_range(5, 5)
|
|
90
|
+
assert rs.ranges == [(1, 4), (6, 10)]
|
|
91
|
+
|
|
92
|
+
def test_remove_start(self):
|
|
93
|
+
"""Test removing start of range."""
|
|
94
|
+
rs = RangeSet()
|
|
95
|
+
rs.add_range(1, 10)
|
|
96
|
+
rs.remove_range(1, 3)
|
|
97
|
+
assert rs.ranges == [(4, 10)]
|
|
98
|
+
|
|
99
|
+
def test_remove_end(self):
|
|
100
|
+
"""Test removing end of range."""
|
|
101
|
+
rs = RangeSet()
|
|
102
|
+
rs.add_range(1, 10)
|
|
103
|
+
rs.remove_range(8, 10)
|
|
104
|
+
assert rs.ranges == [(1, 7)]
|
|
105
|
+
|
|
106
|
+
def test_remove_entire_range(self):
|
|
107
|
+
"""Test removing entire range."""
|
|
108
|
+
rs = RangeSet()
|
|
109
|
+
rs.add_range(1, 10)
|
|
110
|
+
rs.remove_range(1, 10)
|
|
111
|
+
assert rs.ranges == []
|
|
112
|
+
|
|
113
|
+
def test_remove_multiple_ranges(self):
|
|
114
|
+
"""Test removing across multiple ranges."""
|
|
115
|
+
rs = RangeSet()
|
|
116
|
+
rs.add_range(1, 5)
|
|
117
|
+
rs.add_range(10, 15)
|
|
118
|
+
rs.add_range(20, 25)
|
|
119
|
+
rs.remove_range(12, 22)
|
|
120
|
+
assert rs.ranges == [(1, 5), (10, 11), (23, 25)]
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
class TestSetOperations:
|
|
124
|
+
"""Test set operations."""
|
|
125
|
+
|
|
126
|
+
def test_intersection(self):
|
|
127
|
+
"""Test intersection of two range sets."""
|
|
128
|
+
rs1 = RangeSet()
|
|
129
|
+
rs1.add_range(1, 10)
|
|
130
|
+
rs1.add_range(20, 30)
|
|
131
|
+
|
|
132
|
+
rs2 = RangeSet()
|
|
133
|
+
rs2.add_range(5, 15)
|
|
134
|
+
rs2.add_range(25, 35)
|
|
135
|
+
|
|
136
|
+
result = rs1.intersection(rs2)
|
|
137
|
+
assert result.ranges == [(5, 10), (25, 30)]
|
|
138
|
+
|
|
139
|
+
def test_union(self):
|
|
140
|
+
"""Test union of two range sets."""
|
|
141
|
+
rs1 = RangeSet()
|
|
142
|
+
rs1.add_range(1, 5)
|
|
143
|
+
rs1.add_range(10, 15)
|
|
144
|
+
|
|
145
|
+
rs2 = RangeSet()
|
|
146
|
+
rs2.add_range(3, 8)
|
|
147
|
+
rs2.add_range(12, 18)
|
|
148
|
+
|
|
149
|
+
result = rs1.union(rs2)
|
|
150
|
+
assert result.ranges == [(1, 8), (10, 18)]
|
|
151
|
+
|
|
152
|
+
def test_intersects_any(self):
|
|
153
|
+
"""Test checking if any lines intersect."""
|
|
154
|
+
rs = RangeSet()
|
|
155
|
+
rs.add_range(1, 5)
|
|
156
|
+
rs.add_range(10, 15)
|
|
157
|
+
|
|
158
|
+
assert rs.intersects_any({3, 20}) # 3 is in range
|
|
159
|
+
assert rs.intersects_any({12}) # 12 is in range
|
|
160
|
+
assert not rs.intersects_any({6, 7, 8, 20}) # None in range
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
class TestSerialization:
|
|
164
|
+
"""Test serialization and deserialization."""
|
|
165
|
+
|
|
166
|
+
def test_to_compact_string(self):
|
|
167
|
+
"""Test converting to compact string."""
|
|
168
|
+
rs = RangeSet()
|
|
169
|
+
rs.add_range(1, 5)
|
|
170
|
+
rs.add_range(10, 12)
|
|
171
|
+
rs.add_range(20, 20)
|
|
172
|
+
assert rs.to_compact_string() == "1-5,10-12,20"
|
|
173
|
+
|
|
174
|
+
def test_from_compact_string(self):
|
|
175
|
+
"""Test parsing from compact string."""
|
|
176
|
+
rs = RangeSet.from_compact_string("1-5,10-12,20")
|
|
177
|
+
assert rs.ranges == [(1, 5), (10, 12), (20, 20)]
|
|
178
|
+
|
|
179
|
+
def test_roundtrip(self):
|
|
180
|
+
"""Test serialization roundtrip."""
|
|
181
|
+
original = RangeSet()
|
|
182
|
+
original.add_lines({1, 2, 3, 5, 6, 7, 10, 15, 16, 17, 18})
|
|
183
|
+
|
|
184
|
+
serialized = original.to_compact_string()
|
|
185
|
+
restored = RangeSet.from_compact_string(serialized)
|
|
186
|
+
|
|
187
|
+
assert original.ranges == restored.ranges
|
|
188
|
+
|
|
189
|
+
def test_empty_string(self):
|
|
190
|
+
"""Test parsing empty string."""
|
|
191
|
+
rs = RangeSet.from_compact_string("")
|
|
192
|
+
assert rs.ranges == []
|
|
193
|
+
|
|
194
|
+
rs = RangeSet.from_compact_string(" ")
|
|
195
|
+
assert rs.ranges == []
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
class TestEdgeCases:
|
|
199
|
+
"""Test edge cases and error conditions."""
|
|
200
|
+
|
|
201
|
+
def test_reversed_range_normalized(self):
|
|
202
|
+
"""Test that reversed ranges are normalized."""
|
|
203
|
+
rs = RangeSet()
|
|
204
|
+
rs.add_range(10, 1) # Reversed
|
|
205
|
+
assert rs.ranges == [(1, 10)]
|
|
206
|
+
|
|
207
|
+
def test_large_range(self):
|
|
208
|
+
"""Test handling large ranges."""
|
|
209
|
+
rs = RangeSet()
|
|
210
|
+
rs.add_range(1, 1000000)
|
|
211
|
+
assert len(rs) == 1000000
|
|
212
|
+
assert rs.contains(500000)
|
|
213
|
+
|
|
214
|
+
def test_many_small_ranges(self):
|
|
215
|
+
"""Test many small ranges."""
|
|
216
|
+
rs = RangeSet()
|
|
217
|
+
# Add 1000 single-line ranges with gaps
|
|
218
|
+
for i in range(0, 10000, 10):
|
|
219
|
+
rs.add_range(i, i)
|
|
220
|
+
assert len(rs.ranges) == 1000
|
|
221
|
+
assert len(rs) == 1000
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
class TestRealWorldScenario:
|
|
225
|
+
"""Test realistic test coverage scenarios."""
|
|
226
|
+
|
|
227
|
+
def test_pytest_coverage_pattern(self):
|
|
228
|
+
"""Test typical pytest coverage pattern."""
|
|
229
|
+
# Simulate coverage data: function definitions, body, and random executions
|
|
230
|
+
rs = RangeSet()
|
|
231
|
+
|
|
232
|
+
# Function 1: lines 10-25
|
|
233
|
+
rs.add_lines(set(range(10, 26)))
|
|
234
|
+
|
|
235
|
+
# Function 2: lines 30-45
|
|
236
|
+
rs.add_lines(set(range(30, 46)))
|
|
237
|
+
|
|
238
|
+
# Some random lines (maybe imports, globals)
|
|
239
|
+
rs.add_lines({1, 2, 3, 5, 50, 51, 52})
|
|
240
|
+
|
|
241
|
+
# Result should be well-compressed
|
|
242
|
+
assert len(rs.ranges) == 5 # Should merge into 5 ranges
|
|
243
|
+
expected_lines = len(range(10, 26)) + len(range(30, 46)) + 7
|
|
244
|
+
assert len(rs) == expected_lines
|
|
245
|
+
|
|
246
|
+
def test_compression_ratio(self):
|
|
247
|
+
"""Test compression ratio for realistic data."""
|
|
248
|
+
# Simulate 10,000 lines with typical coverage pattern
|
|
249
|
+
rs = RangeSet()
|
|
250
|
+
|
|
251
|
+
# 100 functions, 50 lines each, with 10-line gaps
|
|
252
|
+
for func_start in range(0, 6000, 60):
|
|
253
|
+
rs.add_lines(set(range(func_start, func_start + 50)))
|
|
254
|
+
|
|
255
|
+
# Should have ~100 ranges instead of 5000 lines
|
|
256
|
+
assert len(rs.ranges) < 200
|
|
257
|
+
assert len(rs) == 5000 # 100 functions * 50 lines
|
|
258
|
+
|
|
259
|
+
# Compression ratio should be > 25x
|
|
260
|
+
compression_ratio = len(rs) / len(rs.ranges)
|
|
261
|
+
assert compression_ratio > 25
|