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 +4 -4
- data/DEVELOPMENT.md +2 -0
- data/README.md +8 -0
- data/SMARTEST_DESIGN.md +8 -4
- data/exe/smartest +5 -12
- data/lib/smartest/cli_arguments.rb +66 -0
- data/lib/smartest/runner.rb +4 -3
- data/lib/smartest/test_case.rb +46 -0
- data/lib/smartest/version.rb +1 -1
- data/lib/smartest.rb +1 -0
- data/smartest/smartest_test.rb +86 -0
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 0bd00e8f67e5e3b8fd9931ade12ab688147fb68858b3bf614b2f6aa103b4849d
|
|
4
|
+
data.tar.gz: d3d7293dc006d7ddeafb406c66ef2dd6341af47e6f4cb1ed07f09a0f06667741
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
-
|
|
797
|
+
Smartest.disable_autorun!
|
|
798
|
+
Kernel.prepend Smartest::DSL
|
|
798
799
|
$LOAD_PATH.unshift File.expand_path("smartest", Dir.pwd)
|
|
799
800
|
|
|
800
|
-
|
|
801
|
-
files.each { |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
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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
|
data/lib/smartest/runner.rb
CHANGED
|
@@ -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(@
|
|
16
|
+
@reporter.start(@tests.count)
|
|
16
17
|
|
|
17
18
|
begin
|
|
18
|
-
@
|
|
19
|
+
@tests.each do |test_case|
|
|
19
20
|
result = run_one(test_case)
|
|
20
21
|
results << result
|
|
21
22
|
@reporter.record(result)
|
data/lib/smartest/test_case.rb
CHANGED
|
@@ -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
|
data/lib/smartest/version.rb
CHANGED
data/lib/smartest.rb
CHANGED
data/smartest/smartest_test.rb
CHANGED
|
@@ -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.
|
|
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-
|
|
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
|