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.
@@ -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