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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5df7245c9484fac5c375f5bc3275e2af2ffb4a48e1090a15cfff56188a24047f
4
- data.tar.gz: 23edfd9d58322bdc03dacc04d5986772dfe57690ad5a6481da6bcf21dc604715
3
+ metadata.gz: fdc667523d09a211fe97edcc2175b58b341ab9cfcadaa0aed693d139d95f8319
4
+ data.tar.gz: cc7047cf657da982ed4ba936c1b3cb4cba06ea11e6183b6619e61d08073cdb45
5
5
  SHA512:
6
- metadata.gz: cab54aebaf7b972b7efa70c617bc769109ce74d3aee567717ddd33339d164410a78c4cb6e98ac6277f28828e26179fff8b4cf267d511a1fdbe22ea6b0e5781ca
7
- data.tar.gz: 5a9ef2a87d2ce1ddd88ace0cc19dc6ba3259ba7a500db3abc9f88cb95805ef5a42736c29d7489eb4cdf992d7fec2361bf7b584fbd7532ca88044e8b6add3dde9
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
+ ![testprune report — styled terminal output](assets/report-example.svg)
128
+
129
+ <details>
130
+ <summary>Plain-text (<code>NO_COLOR</code>) version</summary>
126
131
 
127
132
  ```
128
- testprune — test coverage redundancy report
129
- Suite: 4 test(s), framework=minitest
133
+ testprune — coverage redundancy report
134
+ 4 tests · minitest
135
+ ────────────────────────────────────────────────────────────────
136
+
137
+ ● HIGH confidence — safe to remove (1)
138
+ ──────────────────────────────────────────────────────────────
130
139
 
131
- HIGH confidence — safe to remove: 1
132
- [identical] CalculatorTest#test_add_again
133
- at: test/calculator_test.rb:16
134
- reason: identical coverage to CalculatorTest#test_add
135
- kept by: CalculatorTest#test_add
136
- covers: Calculator#add (lib/calculator.rb:4)
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 (structural duplicates): 1
140
- [structural] CalculatorTest#test_positive
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
- Estimated CI savings:
146
- 1 test(s), 0.0132s (~85.7% of 0.0154s test time)
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>
@@ -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 << " covers: #{covered_labels(fp)}"
49
+ out.concat(coverage_text_lines(candidate, fp))
50
50
  out << " #{safety_line(candidate)}"
51
51
  out
52
52
  end
53
53
 
54
- def covered_labels(footprint)
55
- labels = footprint.units.map { |id| @result.label_for(id) }.sort
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
- covers = fp.units.map { |id| @result.label_for(id) }.sort
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)}" \
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Testprune
4
- VERSION = '0.4.0'
4
+ VERSION = '0.4.1'
5
5
  end
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.0
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