smartest 0.1.0.alpha1 → 0.1.0.alpha2

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: b4b99c1edbf3ad101277d9c87de980708ffe37b5665a3b151be6f03803d4837a
4
- data.tar.gz: 6c0d645bd34ccedc0df9ef4fdacb9c3b43133bee5a0f6172ab73c5d9b44c05d1
3
+ metadata.gz: 0bd00e8f67e5e3b8fd9931ade12ab688147fb68858b3bf614b2f6aa103b4849d
4
+ data.tar.gz: d3d7293dc006d7ddeafb406c66ef2dd6341af47e6f4cb1ed07f09a0f06667741
5
5
  SHA512:
6
- metadata.gz: 567e14909b4fd63fe70392014d5f41d964e078ac7f16c46ff64071ae6860eb0eabdad3b1c2c409e51b52ce440b487b2531897ceee40f4d6de5dc79a6eb6297a5
7
- data.tar.gz: ca377cc64560272f1d75f5527ce10014771bf728500d8489abb6a68a6e285c30c1f587b4a24f47a871ead56033a8fb6b4225dbe60c226c6b1288a1d5cdc509fe
6
+ metadata.gz: 9cc36fee51acacabb449c0e8973e92530cd0707a434ed1216db9318378b41473a0ce688e63eb02571da90c72e34874983740907eb93086ff9b5e0c9b9b128df6
7
+ data.tar.gz: 59b6db66b22561660b93608a843313e38569a9eb634286e54539c434052745637773bf1f3e2f86114c5115d1981330471e279cad637577df5e748e26bd186308
data/DEVELOPMENT.md CHANGED
@@ -613,6 +613,8 @@ A practical approach:
613
613
  - `exe/smartest`
614
614
  - load files from ARGV
615
615
  - default glob `smartest/**/*_test.rb`
616
+ - support `path:line` and `path:start-end` filters that run tests whose `test`
617
+ blocks contain or intersect the lines
616
618
  - add `smartest/` to the load path before loading tests
617
619
  - generate a `smartest/test_helper.rb` that loads `smartest/fixtures/**/*.rb`
618
620
  - exit code 0 on success, 1 on failure
data/README.md CHANGED
@@ -88,6 +88,14 @@ You can also pass explicit paths:
88
88
  bundle exec smartest smartest/**/*_test.rb
89
89
  ```
90
90
 
91
+ To run tests by line number, append `:line` or `:start-end` to the file path.
92
+ Smartest runs tests whose `test` blocks contain or intersect the selected lines:
93
+
94
+ ```bash
95
+ bundle exec smartest smartest/user_test.rb:12
96
+ bundle exec smartest smartest/user_test.rb:3-12
97
+ ```
98
+
91
99
  CLI help and version output are available with:
92
100
 
93
101
  ```bash
data/SMARTEST_DESIGN.md CHANGED
@@ -794,15 +794,19 @@ CLI flow:
794
794
  ```ruby
795
795
  require "smartest"
796
796
 
797
- Kernel.include Smartest::DSL
797
+ Smartest.disable_autorun!
798
+ Kernel.prepend Smartest::DSL
798
799
  $LOAD_PATH.unshift File.expand_path("smartest", Dir.pwd)
799
800
 
800
- files = ARGV.empty? ? Dir["smartest/**/*_test.rb"] : ARGV
801
- files.each { |file| require File.expand_path(file) }
801
+ arguments = Smartest::CLIArguments.new(ARGV)
802
+ arguments.files.each { |file| load File.expand_path(file) }
802
803
 
803
- exit Smartest::Runner.new.run
804
+ exit Smartest::Runner.new(tests: arguments.select_tests(Smartest.suite.tests)).run
804
805
  ```
805
806
 
807
+ `Smartest::CLIArguments` should support file paths, shell globs, `path:line`,
808
+ and `path:start-end` filters.
809
+
806
810
  `smartest/autorun` should use `at_exit`.
807
811
 
808
812
  ```ruby
data/exe/smartest CHANGED
@@ -8,6 +8,7 @@ require "smartest"
8
8
  usage = <<~USAGE
9
9
  Usage:
10
10
  smartest [paths...]
11
+ smartest path/to/test_file.rb:line[-line]
11
12
  smartest --init
12
13
  smartest --version
13
14
  smartest --help
@@ -38,21 +39,13 @@ begin
38
39
  test_load_path = File.expand_path("smartest", Dir.pwd)
39
40
  $LOAD_PATH.unshift(test_load_path) if Dir.exist?(test_load_path) && !$LOAD_PATH.include?(test_load_path)
40
41
 
41
- files =
42
- if ARGV.empty?
43
- Dir["smartest/**/*_test.rb"]
44
- else
45
- ARGV.flat_map do |pattern|
46
- matches = Dir[pattern]
47
- matches.empty? ? [pattern] : matches
48
- end.uniq
49
- end
50
-
51
- files.each do |file|
42
+ arguments = Smartest::CLIArguments.new(ARGV)
43
+
44
+ arguments.files.each do |file|
52
45
  load File.expand_path(file)
53
46
  end
54
47
 
55
- exit Smartest::Runner.new.run
48
+ exit Smartest::Runner.new(tests: arguments.select_tests(Smartest.suite.tests)).run
56
49
  rescue Exception => error
57
50
  raise if Smartest.fatal_exception?(error)
58
51
 
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "set"
4
+
5
+ module Smartest
6
+ class CLIArguments
7
+ attr_reader :files, :line_filters
8
+
9
+ def initialize(argv)
10
+ @files = []
11
+ @whole_files = Set.new
12
+ @line_filters = Hash.new { |hash, key| hash[key] = Set.new }
13
+
14
+ parse(argv.empty? ? ["smartest/**/*_test.rb"] : argv)
15
+ end
16
+
17
+ def filter_tests?
18
+ @line_filters.any?
19
+ end
20
+
21
+ def select_tests(tests)
22
+ return tests unless filter_tests?
23
+
24
+ tests.select do |test_case|
25
+ next false unless test_case.location
26
+
27
+ path = File.expand_path(test_case.location.path)
28
+ @whole_files.include?(path) ||
29
+ @line_filters.fetch(path, []).any? { |line_filter| test_case.includes_line_range?(line_filter) }
30
+ end
31
+ end
32
+
33
+ private
34
+
35
+ def parse(argv)
36
+ argv.each do |argument|
37
+ pattern, line_filter = split_line_filter(argument)
38
+ matches = Dir[pattern]
39
+ files = matches.empty? ? [pattern] : matches
40
+
41
+ files.each do |file|
42
+ @files << file
43
+
44
+ path = File.expand_path(file)
45
+ if line_filter
46
+ @line_filters[path].add(line_filter)
47
+ else
48
+ @whole_files.add(path)
49
+ end
50
+ end
51
+ end
52
+
53
+ @files.uniq!
54
+ end
55
+
56
+ def split_line_filter(argument)
57
+ match = argument.match(/\A(.+):(\d+)(?:-(\d+))?\z/)
58
+ return [argument, nil] unless match
59
+
60
+ start_line = match[2].to_i
61
+ end_line = match[3] ? match[3].to_i : start_line
62
+
63
+ [match[1], start_line..end_line]
64
+ end
65
+ end
66
+ end
@@ -2,9 +2,10 @@
2
2
 
3
3
  module Smartest
4
4
  class Runner
5
- def initialize(suite: Smartest.suite, reporter: Reporter.new)
5
+ def initialize(suite: Smartest.suite, reporter: Reporter.new, tests: nil)
6
6
  @suite = suite
7
7
  @reporter = reporter
8
+ @tests = tests || suite.tests
8
9
  end
9
10
 
10
11
  def run
@@ -12,10 +13,10 @@ module Smartest
12
13
  suite_cleanup_errors = []
13
14
  @suite_fixture_set = nil
14
15
 
15
- @reporter.start(@suite.tests.count)
16
+ @reporter.start(@tests.count)
16
17
 
17
18
  begin
18
- @suite.tests.each do |test_case|
19
+ @tests.each do |test_case|
19
20
  result = run_one(test_case)
20
21
  results << result
21
22
  @reporter.record(result)
@@ -14,5 +14,51 @@ module Smartest
14
14
  @location = location
15
15
  @fixture_names = ParameterExtractor.required_keyword_names(block, usage: :test)
16
16
  end
17
+
18
+ def includes_line?(line)
19
+ includes_line_range?(line..line)
20
+ end
21
+
22
+ def includes_line_range?(range)
23
+ return false unless location
24
+
25
+ current_range = line_range
26
+ current_range.begin <= range.end && range.begin <= current_range.end
27
+ end
28
+
29
+ private
30
+
31
+ def line_range
32
+ location.lineno..end_lineno
33
+ end
34
+
35
+ def end_lineno
36
+ @end_lineno ||= inferred_end_lineno
37
+ end
38
+
39
+ def inferred_end_lineno
40
+ code_location = instruction_sequence_metadata[:code_location]
41
+ return code_location[2] if code_location&.length == 4
42
+
43
+ instruction_sequence_line_numbers.max || location.lineno
44
+ rescue StandardError
45
+ location.lineno
46
+ end
47
+
48
+ def instruction_sequence_metadata
49
+ return {} unless defined?(RubyVM::InstructionSequence)
50
+
51
+ sequence = RubyVM::InstructionSequence.of(block)
52
+ metadata = sequence&.to_a&.[](4)
53
+ metadata.is_a?(Hash) ? metadata : {}
54
+ end
55
+
56
+ def instruction_sequence_line_numbers
57
+ return [] unless defined?(RubyVM::InstructionSequence)
58
+
59
+ sequence = RubyVM::InstructionSequence.of(block)
60
+ body = sequence&.to_a&.last
61
+ body.is_a?(Array) ? body.select { |entry| entry.is_a?(Integer) } : []
62
+ end
17
63
  end
18
64
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Smartest
4
- VERSION = "0.1.0.alpha1"
4
+ VERSION = "0.1.0.alpha2"
5
5
  end
data/lib/smartest.rb CHANGED
@@ -19,6 +19,7 @@ require_relative "smartest/test_result"
19
19
  require_relative "smartest/reporter"
20
20
  require_relative "smartest/runner"
21
21
  require_relative "smartest/init_generator"
22
+ require_relative "smartest/cli_arguments"
22
23
 
23
24
  module Smartest
24
25
  class << self
@@ -477,6 +477,91 @@ test("cli loads files and returns failure status") do
477
477
  end
478
478
  end
479
479
 
480
+ test("cli runs tests matching a file line filter") do
481
+ Dir.mktmpdir do |dir|
482
+ smartest_dir = File.join(dir, "smartest")
483
+ FileUtils.mkdir_p(smartest_dir)
484
+ File.write(File.join(smartest_dir, "test_helper.rb"), <<~RUBY)
485
+ require "smartest/autorun"
486
+ RUBY
487
+
488
+ test_file = File.join(smartest_dir, "sample_test.rb")
489
+ File.write(test_file, <<~RUBY)
490
+ require "test_helper"
491
+
492
+ test("line one") do
493
+ expect(1).to eq(1)
494
+ end
495
+
496
+ test("line two") do
497
+ expect(2).to eq(2)
498
+ end
499
+ RUBY
500
+ line_number = File.readlines(test_file).find_index { |line| line.include?("expect(2)") } + 1
501
+
502
+ stdout, stderr, status = Open3.capture3(
503
+ { "RUBYLIB" => File.expand_path("../lib", __dir__) },
504
+ "ruby",
505
+ File.expand_path("../exe/smartest", __dir__),
506
+ "smartest/sample_test.rb:#{line_number}",
507
+ chdir: dir
508
+ )
509
+
510
+ expect(status.success?).to eq(true)
511
+ expect(stderr).to eq("")
512
+ expect(stdout).to include("Running 1 test")
513
+ expect(stdout).not_to include("line one")
514
+ expect(stdout).to include("line two")
515
+ expect(stdout).to include("1 test, 1 passed, 0 failed")
516
+ end
517
+ end
518
+
519
+ test("cli runs tests intersecting a file line range filter") do
520
+ Dir.mktmpdir do |dir|
521
+ smartest_dir = File.join(dir, "smartest")
522
+ FileUtils.mkdir_p(smartest_dir)
523
+ File.write(File.join(smartest_dir, "test_helper.rb"), <<~RUBY)
524
+ require "smartest/autorun"
525
+ RUBY
526
+
527
+ test_file = File.join(smartest_dir, "sample_test.rb")
528
+ File.write(test_file, <<~RUBY)
529
+ require "test_helper"
530
+
531
+ test("range one") do
532
+ expect(1).to eq(1)
533
+ end
534
+
535
+ test("range two") do
536
+ expect(2).to eq(2)
537
+ end
538
+
539
+ test("range three") do
540
+ expect(3).to eq(3)
541
+ end
542
+ RUBY
543
+ lines = File.readlines(test_file)
544
+ start_line = lines.find_index { |line| line.include?("expect(1)") } + 1
545
+ end_line = lines.find_index { |line| line.include?("expect(2)") } + 1
546
+
547
+ stdout, stderr, status = Open3.capture3(
548
+ { "RUBYLIB" => File.expand_path("../lib", __dir__) },
549
+ "ruby",
550
+ File.expand_path("../exe/smartest", __dir__),
551
+ "smartest/sample_test.rb:#{start_line}-#{end_line}",
552
+ chdir: dir
553
+ )
554
+
555
+ expect(status.success?).to eq(true)
556
+ expect(stderr).to eq("")
557
+ expect(stdout).to include("Running 2 tests")
558
+ expect(stdout).to include("range one")
559
+ expect(stdout).to include("range two")
560
+ expect(stdout).not_to include("range three")
561
+ expect(stdout).to include("2 tests, 2 passed, 0 failed")
562
+ end
563
+ end
564
+
480
565
  test("cli default suite ignores minitest-style test directory") do
481
566
  Dir.mktmpdir do |dir|
482
567
  smartest_dir = File.join(dir, "smartest")
@@ -541,6 +626,7 @@ test("cli prints help") do
541
626
  expect(stderr).to eq("")
542
627
  expect(stdout).to include("Usage:")
543
628
  expect(stdout).to include("smartest [paths...]")
629
+ expect(stdout).to include("smartest path/to/test_file.rb:line[-line]")
544
630
  expect(stdout).to include("smartest --init")
545
631
  expect(stdout).to include("smartest/**/*_test.rb")
546
632
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: smartest
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0.alpha1
4
+ version: 0.1.0.alpha2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yusuke Iwaki
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-04-26 00:00:00.000000000 Z
11
+ date: 2026-04-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake
@@ -42,6 +42,7 @@ files:
42
42
  - exe/smartest
43
43
  - lib/smartest.rb
44
44
  - lib/smartest/autorun.rb
45
+ - lib/smartest/cli_arguments.rb
45
46
  - lib/smartest/dsl.rb
46
47
  - lib/smartest/errors.rb
47
48
  - lib/smartest/execution_context.rb