testprune 0.4.0 → 0.4.1
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.
- checksums.yaml +4 -4
- data/README.md +33 -17
- data/assets/report-example.svg +30 -0
- data/lib/testprune/report.rb +27 -3
- data/lib/testprune/ui/report_renderer.rb +34 -3
- data/lib/testprune/version.rb +1 -1
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: fdc667523d09a211fe97edcc2175b58b341ab9cfcadaa0aed693d139d95f8319
|
|
4
|
+
data.tar.gz: cc7047cf657da982ed4ba936c1b3cb4cba06ea11e6183b6619e61d08073cdb45
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7d7cfef25ef87d93ea0e928409a9dd8aa778fac90ad56e501a2a29de1322b02656608a6b8761902b2e801773b8c900126f65695995d464fda44d84a71cdef5e2
|
|
7
|
+
data.tar.gz: 9ae8e0da87b1f18a54fb2b4d4a3f43875380af913694eaabd5aac78fbe2ca3d927709edb557ba9b6b9221e6f556ce37b9570d91f8359e41cfa0d99c1a880aacc
|
data/README.md
CHANGED
|
@@ -122,30 +122,46 @@ testprune report --json # machine-readable (for CI dashboards)
|
|
|
122
122
|
testprune report -s app -s lib
|
|
123
123
|
```
|
|
124
124
|
|
|
125
|
-
**Example output** (against the bundled calculator fixture):
|
|
125
|
+
**Example output** (against the bundled calculator fixture). In an interactive terminal the report is colorized and boxed via lipgloss:
|
|
126
|
+
|
|
127
|
+

|
|
128
|
+
|
|
129
|
+
<details>
|
|
130
|
+
<summary>Plain-text (<code>NO_COLOR</code>) version</summary>
|
|
126
131
|
|
|
127
132
|
```
|
|
128
|
-
testprune —
|
|
129
|
-
|
|
133
|
+
testprune — coverage redundancy report
|
|
134
|
+
4 tests · minitest
|
|
135
|
+
────────────────────────────────────────────────────────────────
|
|
136
|
+
|
|
137
|
+
● HIGH confidence — safe to remove (1)
|
|
138
|
+
──────────────────────────────────────────────────────────────
|
|
130
139
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
✓ safe — every covered unit remains covered by a retained test
|
|
140
|
+
[identical] CalculatorTest#test_add_again
|
|
141
|
+
at: test/calculator_test.rb:16
|
|
142
|
+
reason: identical coverage to CalculatorTest#test_add
|
|
143
|
+
kept by: CalculatorTest#test_add
|
|
144
|
+
both cover: Calculator#add (lib/calculator.rb:4)
|
|
145
|
+
✓ safe — every covered unit is retained by another test
|
|
138
146
|
|
|
139
|
-
MEDIUM confidence — review
|
|
140
|
-
|
|
141
|
-
at: test/calculator_test.rb:20
|
|
142
|
-
reason: test body structurally identical to CalculatorTest#test_nonpositive
|
|
143
|
-
· review-only — not auto-applied
|
|
147
|
+
● MEDIUM confidence — review (1)
|
|
148
|
+
──────────────────────────────────────────────────────────────
|
|
144
149
|
|
|
145
|
-
|
|
146
|
-
|
|
150
|
+
[structural] CalculatorTest#test_positive
|
|
151
|
+
at: test/calculator_test.rb:20
|
|
152
|
+
reason: test body structurally identical to CalculatorTest#test_nonpositive
|
|
153
|
+
covers: Calculator#sign (lib/calculator.rb:8)
|
|
154
|
+
· review-only — not auto-applied
|
|
155
|
+
|
|
156
|
+
Estimated CI savings
|
|
157
|
+
1 test(s) · 0.0132s saved · ~85.7% of suite
|
|
158
|
+
Note: wall-clock savings lower on parallel CI runners
|
|
159
|
+
|
|
160
|
+
Run testprune apply to review and emit a removal patch.
|
|
147
161
|
```
|
|
148
162
|
|
|
163
|
+
</details>
|
|
164
|
+
|
|
149
165
|
### Step 3 — Apply
|
|
150
166
|
|
|
151
167
|
```sh
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
<svg viewBox="0 0 661 668" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="testprune report — styled terminal output">
|
|
2
|
+
<rect width="661" height="668" fill="#0d1117" rx="14"/>
|
|
3
|
+
<g font-family="ui-monospace,'SF Mono',Menlo,Consolas,monospace" font-size="13" xml:space="preserve">
|
|
4
|
+
<text x="22" y="34"><tspan fill="#7D56F4">╭──────────────────────────────────────────╮</tspan></text>
|
|
5
|
+
<text x="22" y="54"><tspan fill="#7D56F4">│ </tspan><tspan fill="#E2E8F0"> testprune — coverage redundancy report</tspan><tspan fill="#E2E8F0"> </tspan><tspan fill="#7D56F4">│</tspan></text>
|
|
6
|
+
<text x="22" y="74"><tspan fill="#7D56F4">│ </tspan><tspan fill="#E2E8F0"> 4 tests · minitest</tspan><tspan fill="#E2E8F0"> </tspan><tspan fill="#7D56F4">│</tspan></text>
|
|
7
|
+
<text x="22" y="94"><tspan fill="#7D56F4">╰──────────────────────────────────────────╯</tspan></text>
|
|
8
|
+
<text x="22" y="134"><tspan fill="#22C55E" font-weight="700"> ● HIGH confidence — safe to remove</tspan><tspan fill="#9CA3AF"> (1)</tspan></text>
|
|
9
|
+
<text x="22" y="154"><tspan fill="#3D3D5C"> ──────────────────────────────────────────────────────────────</tspan></text>
|
|
10
|
+
<text x="22" y="194"><tspan fill="#7D56F4"> [identical]</tspan><tspan fill="#E2E8F0"> CalculatorTest#test_add_again</tspan></text>
|
|
11
|
+
<text x="22" y="214"><tspan fill="#3D3D5C"> at: </tspan><tspan fill="#9CA3AF">test/calculator_test.rb:16</tspan></text>
|
|
12
|
+
<text x="22" y="234"><tspan fill="#3D3D5C"> reason: </tspan><tspan fill="#9CA3AF">identical coverage to CalculatorTest#test_add</tspan></text>
|
|
13
|
+
<text x="22" y="254"><tspan fill="#3D3D5C"> kept by: CalculatorTest#test_add</tspan></text>
|
|
14
|
+
<text x="22" y="274"><tspan fill="#3D3D5C"> both cover: Calculator#add (lib/calculator.rb:4)</tspan></text>
|
|
15
|
+
<text x="22" y="294"><tspan fill="#22C55E"> ✓ safe — every covered unit is retained by another test</tspan></text>
|
|
16
|
+
<text x="22" y="334"><tspan fill="#F59E0B" font-weight="700"> ● MEDIUM confidence — review</tspan><tspan fill="#9CA3AF"> (1)</tspan></text>
|
|
17
|
+
<text x="22" y="354"><tspan fill="#3D3D5C"> ──────────────────────────────────────────────────────────────</tspan></text>
|
|
18
|
+
<text x="22" y="394"><tspan fill="#7D56F4"> [structural]</tspan><tspan fill="#E2E8F0"> CalculatorTest#test_positive</tspan></text>
|
|
19
|
+
<text x="22" y="414"><tspan fill="#3D3D5C"> at: </tspan><tspan fill="#9CA3AF">test/calculator_test.rb:20</tspan></text>
|
|
20
|
+
<text x="22" y="434"><tspan fill="#3D3D5C"> reason: </tspan><tspan fill="#9CA3AF">test body structurally identical to CalculatorTest#test_nonpositive</tspan></text>
|
|
21
|
+
<text x="22" y="454"><tspan fill="#3D3D5C"> covers: Calculator#sign (lib/calculator.rb:8)</tspan></text>
|
|
22
|
+
<text x="22" y="474"><tspan fill="#3D3D5C"> · review-only — not auto-applied</tspan></text>
|
|
23
|
+
<text x="22" y="514"><tspan fill="#7D56F4">╭─────────────────────────────────────────────────────────╮</tspan></text>
|
|
24
|
+
<text x="22" y="534"><tspan fill="#7D56F4">│ </tspan><tspan fill="#E2E8F0"> Estimated CI savings</tspan><tspan fill="#E2E8F0"> </tspan><tspan fill="#7D56F4">│</tspan></text>
|
|
25
|
+
<text x="22" y="554"><tspan fill="#7D56F4">│ </tspan><tspan fill="#22C55E"> 1 test(s)</tspan><tspan fill="#9CA3AF"> · </tspan><tspan fill="#22C55E">0.0132s saved</tspan><tspan fill="#9CA3AF"> · </tspan><tspan fill="#22C55E">~85.7% of suite</tspan><tspan fill="#E2E8F0"> </tspan><tspan fill="#7D56F4">│</tspan></text>
|
|
26
|
+
<text x="22" y="574"><tspan fill="#7D56F4">│ </tspan><tspan fill="#9CA3AF"> Note: wall-clock savings lower on parallel CI runners</tspan><tspan fill="#E2E8F0"> </tspan><tspan fill="#7D56F4">│</tspan></text>
|
|
27
|
+
<text x="22" y="594"><tspan fill="#7D56F4">╰─────────────────────────────────────────────────────────╯</tspan></text>
|
|
28
|
+
<text x="22" y="634"><tspan fill="#9CA3AF"> Run </tspan><tspan fill="#7D56F4">testprune apply</tspan><tspan fill="#9CA3AF"> to review and emit a removal patch.</tspan></text>
|
|
29
|
+
</g>
|
|
30
|
+
</svg>
|
data/lib/testprune/report.rb
CHANGED
|
@@ -46,13 +46,37 @@ module Testprune
|
|
|
46
46
|
out << " at: #{fp.file}:#{fp.line}" if fp.file
|
|
47
47
|
out << " reason: #{candidate.reason}"
|
|
48
48
|
out << " kept by: #{candidate.kept_by.join(', ')}" unless candidate.kept_by.empty?
|
|
49
|
-
out
|
|
49
|
+
out.concat(coverage_text_lines(candidate, fp))
|
|
50
50
|
out << " #{safety_line(candidate)}"
|
|
51
51
|
out
|
|
52
52
|
end
|
|
53
53
|
|
|
54
|
-
def
|
|
55
|
-
|
|
54
|
+
def coverage_text_lines(candidate, fp)
|
|
55
|
+
case candidate.group
|
|
56
|
+
when :identical
|
|
57
|
+
[" both cover: #{covered_labels(fp.units)}"]
|
|
58
|
+
when :subset
|
|
59
|
+
keeper = find_keeper(candidate)
|
|
60
|
+
lines = [" candidate covers: #{covered_labels(fp.units)}"]
|
|
61
|
+
if keeper
|
|
62
|
+
extra = keeper.units - fp.units
|
|
63
|
+
lines << " keeper adds: #{covered_labels(extra)}" unless extra.empty?
|
|
64
|
+
end
|
|
65
|
+
lines
|
|
66
|
+
else
|
|
67
|
+
[" covers: #{covered_labels(fp.units)}"]
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def find_keeper(candidate)
|
|
72
|
+
return nil if candidate.kept_by.empty?
|
|
73
|
+
return nil unless @result.respond_to?(:detector_result)
|
|
74
|
+
|
|
75
|
+
@result.detector_result.footprints.find { |f| f.id == candidate.kept_by.first }
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def covered_labels(units)
|
|
79
|
+
labels = units.map { |id| @result.label_for(id) }.sort
|
|
56
80
|
labels.size <= 4 ? labels.join('; ') : "#{labels.first(4).join('; ')} (+#{labels.size - 4} more)"
|
|
57
81
|
end
|
|
58
82
|
|
|
@@ -101,9 +101,7 @@ module Testprune
|
|
|
101
101
|
lines << " #{styled('kept by: ', Styles::DIM_TEXT)}#{styled(candidate.kept_by.join(', '), Styles::DIM_TEXT)}"
|
|
102
102
|
end
|
|
103
103
|
|
|
104
|
-
|
|
105
|
-
covers_text = covers.size <= 4 ? covers.join(' · ') : "#{covers.first(4).join(' · ')} (+#{covers.size - 4} more)"
|
|
106
|
-
lines << " #{styled('covers: ', Styles::DIM_TEXT)}#{styled(covers_text, Styles::DIM_TEXT)}"
|
|
104
|
+
lines.concat(coverage_detail_lines(candidate, fp))
|
|
107
105
|
|
|
108
106
|
safety = case candidate.safe
|
|
109
107
|
when true then styled(' ✓ safe — every covered unit is retained by another test', Styles::SAFE_LINE)
|
|
@@ -138,6 +136,39 @@ module Testprune
|
|
|
138
136
|
tty? ? Styles::SUCCESS_BOX.render(msg) : msg
|
|
139
137
|
end
|
|
140
138
|
|
|
139
|
+
def coverage_detail_lines(candidate, fp)
|
|
140
|
+
case candidate.group
|
|
141
|
+
when :identical
|
|
142
|
+
[coverage_line('both cover', fp.units)]
|
|
143
|
+
when :subset
|
|
144
|
+
keeper = keeper_footprint(candidate)
|
|
145
|
+
lines = [coverage_line('candidate covers', fp.units)]
|
|
146
|
+
if keeper
|
|
147
|
+
extra = keeper.units - fp.units
|
|
148
|
+
lines << coverage_line('keeper adds', extra) unless extra.empty?
|
|
149
|
+
end
|
|
150
|
+
lines
|
|
151
|
+
else
|
|
152
|
+
[coverage_line('covers', fp.units)]
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def coverage_line(label, units)
|
|
157
|
+
" #{styled("#{label}: ", Styles::DIM_TEXT)}#{styled(format_units(units), Styles::DIM_TEXT)}"
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def keeper_footprint(candidate)
|
|
161
|
+
return nil if candidate.kept_by.empty?
|
|
162
|
+
return nil unless @result.respond_to?(:detector_result)
|
|
163
|
+
|
|
164
|
+
@result.detector_result.footprints.find { |f| f.id == candidate.kept_by.first }
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
def format_units(units)
|
|
168
|
+
labels = units.map { |id| @result.label_for(id) }.sort
|
|
169
|
+
labels.size <= 4 ? labels.join(' · ') : "#{labels.first(4).join(' · ')} (+#{labels.size - 4} more)"
|
|
170
|
+
end
|
|
171
|
+
|
|
141
172
|
def cta_line
|
|
142
173
|
" #{styled('Run ', Styles::META_TEXT)}" \
|
|
143
174
|
"#{styled('testprune apply', Styles::PURPLE_TEXT)}" \
|
data/lib/testprune/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: testprune
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.4.
|
|
4
|
+
version: 0.4.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Seth MacPherson
|
|
@@ -57,6 +57,7 @@ files:
|
|
|
57
57
|
- LICENSE
|
|
58
58
|
- README.md
|
|
59
59
|
- assets/quickstart.svg
|
|
60
|
+
- assets/report-example.svg
|
|
60
61
|
- exe/testprune
|
|
61
62
|
- lib/testprune.rb
|
|
62
63
|
- lib/testprune/adapters/minitest.rb
|