testprune 0.1.0 → 0.3.0

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: d3484c32e59411fd94af756cfaec78df0a07c6ce0f7f7dfaf16cf82c6745d84f
4
- data.tar.gz: 47e74f078c45818060d393c08473a66275fd5a002e750a24ed02ca6b47d8f412
3
+ metadata.gz: e0524c23f51b9afdbb9731dee285f81be5571abcff4730b09194865edcb01d0d
4
+ data.tar.gz: 648d2e72e49dc16a083b7ca116889e4694fdaa72e6e1337207bdc7da7376260f
5
5
  SHA512:
6
- metadata.gz: 0c9cc56ac84e87e994165c3abce1238a84924f3ecf980042ee876b0c0b6fa551021780d23a903494298f37bd248c546d1655c282285c98f6a2111ba37a9ab67b
7
- data.tar.gz: f6c4748cde9a7e89dbaaeb67cc609e8c17de9173b75c723d0f0c4f22f84e77034b52a51187233418778920ddec58953585942002b10771a8ce726201632522fd
6
+ metadata.gz: 762a0cf8e298644d06094814b81e9579312c16247a649013da282207623e88349a0918a95657f9a33125fb9b0a029369630071806b484b8c5869f4979dafc858
7
+ data.tar.gz: a245409289d680cfe06bc08ad2afdc004ae1d542f9e2895e70d0840834852f8ebf75d95215129f1afd7c58d84b34925864d5af02ae34ae933b70c34aa35a0a75
data/README.md CHANGED
@@ -11,9 +11,12 @@
11
11
  ```sh
12
12
  gem install testprune # no Gemfile change required
13
13
 
14
- testprune run # runs your suite once, records per-test coverage
14
+ testprune scan # runs your suite once, records per-test coverage
15
15
  testprune report # see what's redundant — read-only
16
16
  testprune apply # approve removals → .patch file → git apply
17
+
18
+ # or all at once:
19
+ testprune prune # scan + apply in a single step
17
20
  ```
18
21
 
19
22
  Works with **Minitest** and **RSpec**. No config files. No changes to your project required.
@@ -54,7 +57,7 @@ end
54
57
 
55
58
  ```sh
56
59
  bundle install
57
- bundle exec testprune run
60
+ bundle exec testprune scan
58
61
  ```
59
62
 
60
63
  ---
@@ -64,20 +67,46 @@ bundle exec testprune run
64
67
  ### Step 1 — Capture
65
68
 
66
69
  ```sh
67
- # Autodetect: runs `rspec` if spec/ exists, `rake test` otherwise
68
- testprune run
70
+ # No args: autodetects framework; prompts to exclude slow/integration folders
71
+ # (any test/ subdirectory matching: selenium, request, piper, integration)
72
+ testprune scan
73
+
74
+ # Positional paths — no `--` needed
75
+ testprune scan test/api test/services test/models
76
+ testprune scan test/models test/jobs -s app -s lib
69
77
 
70
78
  # Explicit command (pass after --)
71
- testprune run -- bundle exec rspec spec/models
72
- testprune run -- bundle exec ruby -Itest test/my_test.rb
73
- testprune run -- bundle exec rails test test/controllers/
79
+ testprune scan -- bundle exec rspec spec/models
80
+ testprune scan -- bundle exec ruby -Itest test/my_test.rb
81
+ testprune scan -- bundle exec rails test test/controllers/
74
82
 
75
83
  # Restrict which source files are analyzed (-s is repeatable)
76
- testprune run -s app -s lib -s packs
84
+ testprune scan -s app -s lib -s packs
85
+ ```
86
+
87
+ When run with no arguments, `testprune scan` checks your `test/` directory and flags any subdirectories whose names contain `selenium`, `request`, `piper`, or `integration` — folders that tend to be slow, browser-driven, or external and are generally poor candidates for coverage analysis. You'll be prompted to exclude them before the run starts:
88
+
89
+ ```
90
+ testprune: found folders that may be slow or integration-heavy:
91
+ test/integration
92
+ test/selenium
93
+ Include them in this run? [y/N]:
77
94
  ```
78
95
 
96
+ Answering `N` (the default) scopes the run to the remaining folders using `rails test`. Answering `y` proceeds with the normal autodetected command.
97
+
79
98
  Writes `tmp/.testprune/run.json` — per-test coverage deltas and wall times. This is the only step that boots your suite.
80
99
 
100
+ ### One-step workflow
101
+
102
+ ```sh
103
+ testprune prune # scan + apply in sequence — boots suite, then prompts for approval
104
+ testprune prune test/models test/jobs -s app -s lib
105
+ testprune prune -- bundle exec rails test test/models/
106
+ ```
107
+
108
+ `prune` runs `scan` to capture coverage data, then immediately runs `apply` — which prints the report and prompts before writing any patch. Use this when you want the full workflow without running three separate commands.
109
+
81
110
  ### Step 2 — Report
82
111
 
83
112
  ```sh
@@ -154,7 +183,7 @@ Removed tests are **commented out** (not deleted) with a reason annotation — y
154
183
 
155
184
 
156
185
  ┌─────────────────────────────────────────────────────────────────────┐
157
- │ CAPTURE (testprune run)
186
+ │ CAPTURE (testprune scan)
158
187
  │ │
159
188
  │ Coverage.setup(lines:, branches:, methods:) │
160
189
  │ │ │
@@ -318,37 +347,47 @@ removal** — testprune can't tell what it uniquely exercises.
318
347
  ### Minitest project (standard layout)
319
348
 
320
349
  ```sh
321
- testprune run -s app -s lib
350
+ testprune scan -s app -s lib
322
351
  testprune report -s app -s lib
323
352
  testprune apply -s app -s lib
353
+
354
+ # or all at once:
355
+ testprune prune -s app -s lib
324
356
  ```
325
357
 
326
358
  ### RSpec project
327
359
 
328
360
  ```sh
329
- testprune run -s app -s lib -- bundle exec rspec
361
+ testprune scan -s app -s lib -- bundle exec rspec
330
362
  testprune report -s app -s lib
331
363
  ```
332
364
 
333
365
  ### Rails app
334
366
 
335
367
  ```sh
336
- # Run a specific directory (never run the whole suite without a path)
337
- testprune run -s app -s lib -s packs -- bundle exec rails test test/models/
368
+ # Scan with no args testprune will prompt to exclude selenium/integration folders
369
+ testprune scan -s app -s lib
370
+
371
+ # Scope to specific directories (positional args, no -- needed)
372
+ testprune scan -s app -s lib test/models
373
+ testprune scan -s app -s lib test/models test/jobs test/services
338
374
 
339
- # Multiple passes capture incrementally by domain, analyze together
340
- testprune run -s app -- bundle exec rails test test/models/
341
- testprune run -s app -- bundle exec rails test test/controllers/
342
- # run.json is overwritten on each `testprune run`; analyze each pass separately
375
+ # Explicit command if you need full control
376
+ testprune scan -s app -s lib -s packs -- bundle exec rails test test/models/
377
+
378
+ # Full workflow in one step
379
+ testprune prune -s app -s lib test/models test/jobs
343
380
  ```
344
381
 
382
+ > **Note:** `run.json` is overwritten on each `testprune scan`. Run report/apply after each capture pass.
383
+
345
384
  ### Rails app with Spring
346
385
 
347
386
  Spring preloads your app but forks the test process — the forked child doesn't
348
387
  inherit `RUBYOPT` where testprune's autostart lives. Disable it for the run:
349
388
 
350
389
  ```sh
351
- DISABLE_SPRING=1 testprune run -s app -s lib -- bundle exec rails test test/models/
390
+ DISABLE_SPRING=1 testprune scan -s app -s lib -- bundle exec rails test test/models/
352
391
  ```
353
392
 
354
393
  ### Projects using SimpleCov (or other coverage gems)
@@ -379,11 +418,11 @@ rv run --ruby 3.2 env RUBYOPT="-I$(gem contents testprune | grep autostart | xar
379
418
 
380
419
  # Simpler: install testprune under the managed ruby so RUBYOPT is not needed
381
420
  rv run --ruby 3.2 gem install testprune
382
- rv run --ruby 3.2 bundle exec testprune run
421
+ rv run --ruby 3.2 bundle exec testprune scan
383
422
  ```
384
423
 
385
424
  The easiest path is always to install testprune under the same Ruby that runs
386
- your suite. If `TESTPRUNE_DEBUG=1 testprune run` prints nothing from `[testprune-debug]`,
425
+ your suite. If `TESTPRUNE_DEBUG=1 testprune scan` prints nothing from `[testprune-debug]`,
387
426
  the autostart never loaded — this is the cause.
388
427
 
389
428
  ### Monorepo / packs
@@ -392,7 +431,7 @@ Run each pack separately and analyze with its source path:
392
431
 
393
432
  ```sh
394
433
  # Capture one pack
395
- testprune run -s packs/tenancy/app -- \
434
+ testprune scan -s packs/tenancy/app -- \
396
435
  bundle exec rails test packs/tenancy/test/
397
436
 
398
437
  # Report for that pack
@@ -427,7 +466,7 @@ jobs:
427
466
  ruby-version: '3.2'
428
467
  bundler-cache: true
429
468
  - run: gem install testprune
430
- - run: testprune run -s app -s lib -- bundle exec rails test test/models/
469
+ - run: testprune scan -s app -s lib -- bundle exec rails test test/models/
431
470
  - run: testprune report -s app -s lib --json > testprune-report.json
432
471
  - uses: actions/upload-artifact@v4
433
472
  with:
@@ -462,9 +501,9 @@ testprune report -s app -s lib || true
462
501
 
463
502
  | Variable | Effect |
464
503
  |----------|--------|
465
- | `TESTPRUNE_ROOT` | Set the project root (default: `Dir.pwd`). Set automatically by `testprune run`. |
466
- | `TESTPRUNE_SOURCE_PATHS` | Colon-separated source paths. Set automatically by `testprune run`. |
467
- | `TESTPRUNE_OUTPUT_DIR` | Output directory. Set automatically by `testprune run`. |
504
+ | `TESTPRUNE_ROOT` | Set the project root (default: `Dir.pwd`). Set automatically by `testprune scan`. |
505
+ | `TESTPRUNE_SOURCE_PATHS` | Colon-separated source paths. Set automatically by `testprune scan`. |
506
+ | `TESTPRUNE_OUTPUT_DIR` | Output directory. Set automatically by `testprune scan`. |
468
507
  | `TESTPRUNE_DEBUG` | Print adapter-load diagnostics (`[testprune-debug] autostart loaded in pid …`). Useful when capture produces no `run.json`. |
469
508
  | `DISABLE_SPRING` | Disable Spring preloader so the test process inherits testprune's instrumentation. |
470
509
 
@@ -494,7 +533,7 @@ overhead. On very large suites (10k+ tests) this is noticeable but acceptable
494
533
  it's mitigated by restricting `--source` to the paths you care about.
495
534
 
496
535
  **run.json is machine-local.** Coverage paths are absolute. Don't run
497
- `testprune run` on CI and `testprune report` on a laptop with a different home
536
+ `testprune scan` on CI and `testprune report` on a laptop with a different home
498
537
  directory — the paths won't match. Always run all three commands on the same
499
538
  machine.
500
539
 
@@ -29,7 +29,7 @@ module Testprune
29
29
  end
30
30
 
31
31
  unless File.exist?(@config.run_file)
32
- raise Error, "no captured data at #{@config.run_file}. Run `testprune run` first."
32
+ raise Error, "no coverage data found. Run `testprune run` first to capture per-test coverage."
33
33
  end
34
34
 
35
35
  run = begin
data/lib/testprune/cli.rb CHANGED
@@ -9,18 +9,22 @@ module Testprune
9
9
  # report analyzes run.json and prints grouped candidates (read-only)
10
10
  # apply prompts for approval, then writes a removal patch (never edits in place)
11
11
  class CLI
12
+ NOISY_PATTERNS = %w[selenium request piper integration].freeze
13
+
12
14
  BANNER = <<~TXT
13
15
  testprune — audit a Ruby test suite for redundant coverage
14
16
 
15
17
  Usage:
16
- testprune run [options] [-- <test command>]
18
+ testprune scan [options] [-- <test command>]
17
19
  testprune report [options]
18
20
  testprune apply [options]
21
+ testprune prune [options] [-- <test command>]
19
22
 
20
23
  Commands:
21
- run Run the target suite instrumented; capture per-test coverage + timing
24
+ scan Run the target suite instrumented; capture per-test coverage + timing
22
25
  report Analyze captured data and print removal candidates (read-only)
23
26
  apply Review candidates, ask for approval, emit a git-applyable patch
27
+ prune Run scan + apply in one step (the full workflow)
24
28
 
25
29
  Options:
26
30
  -s, --source PATH Source dir to analyze (repeatable; default: app, lib)
@@ -40,9 +44,10 @@ module Testprune
40
44
  argv = argv.dup
41
45
  command = argv.shift
42
46
  case command
43
- when 'run' then cmd_run(argv)
47
+ when 'scan' then cmd_scan(argv)
44
48
  when 'report' then cmd_report(argv)
45
49
  when 'apply' then cmd_apply(argv)
50
+ when 'prune' then cmd_prune(argv)
46
51
  when '-v', '--version' then puts(Testprune::VERSION)
47
52
  when nil, '-h', '--help' then puts(BANNER)
48
53
  else
@@ -88,12 +93,46 @@ module Testprune
88
93
  end
89
94
  end
90
95
 
91
- def cmd_run(argv)
96
+ def cmd_scan(argv)
92
97
  cmd_argv, test_command = split_test_command(argv)
93
- opts, = parse_options(cmd_argv)
98
+ opts, paths = parse_options(cmd_argv)
94
99
  apply_config(opts)
95
100
  require_relative 'runner'
96
- Runner.new(Testprune.config).call(test_command)
101
+ runner = Runner.new(Testprune.config)
102
+ if test_command.nil? && paths.empty?
103
+ test_command = prompt_noisy_exclusions(runner)
104
+ elsif test_command.nil?
105
+ test_command = runner.command_for_paths(paths)
106
+ end
107
+ runner.call(test_command)
108
+ end
109
+
110
+ def prompt_noisy_exclusions(runner)
111
+ test_dir = File.join(Testprune.config.root, 'test')
112
+ return nil unless File.directory?(test_dir)
113
+
114
+ all_subdirs = Dir.children(test_dir)
115
+ .select { |d| File.directory?(File.join(test_dir, d)) }
116
+ .sort
117
+ noisy = all_subdirs.select { |d| NOISY_PATTERNS.any? { |p| d.downcase.include?(p) } }
118
+ return nil if noisy.empty?
119
+
120
+ warn("testprune: found folders that may be slow or integration-heavy:")
121
+ noisy.each { |d| warn(" test/#{d}") }
122
+ $stderr.print("Include them in this run? [y/N]: ")
123
+ answer = $stdin.gets&.strip&.downcase
124
+
125
+ return nil if %w[y yes].include?(answer)
126
+
127
+ kept = (all_subdirs - noisy).map { |d| "test/#{d}" }
128
+ return nil if kept.empty?
129
+
130
+ runner.command_for_paths(kept)
131
+ end
132
+
133
+ def cmd_prune(argv)
134
+ cmd_scan(argv)
135
+ cmd_apply([])
97
136
  end
98
137
 
99
138
  def cmd_report(argv)
@@ -39,6 +39,18 @@ module Testprune
39
39
  ok
40
40
  end
41
41
 
42
+ def command_for_paths(paths)
43
+ bundler = File.exist?(File.join(@config.root, 'Gemfile'))
44
+ prefix = bundler ? %w[bundle exec] : []
45
+ if File.directory?(File.join(@config.root, 'spec'))
46
+ prefix + %w[rspec] + paths
47
+ elsif File.exist?(File.join(@config.root, 'bin', 'rails'))
48
+ prefix + %w[rails test] + paths
49
+ else
50
+ prefix + %w[rake test] + paths
51
+ end
52
+ end
53
+
42
54
  private
43
55
 
44
56
  def env
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Testprune
4
- VERSION = '0.1.0'
4
+ VERSION = '0.3.0'
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.1.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Seth MacPherson
@@ -83,7 +83,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
83
83
  - !ruby/object:Gem::Version
84
84
  version: '0'
85
85
  requirements: []
86
- rubygems_version: 3.6.9
86
+ rubygems_version: 4.0.3
87
87
  specification_version: 4
88
88
  summary: Audits a Ruby test suite for duplicate/redundant coverage using Prism AST
89
89
  + Coverage data