tldr 0.4.0 → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +17 -1
- data/README.md +27 -5
- data/lib/tldr/argv_parser.rb +12 -11
- data/lib/tldr/class_util.rb +15 -0
- data/lib/tldr/parallelizer.rb +10 -4
- data/lib/tldr/path_util.rb +43 -0
- data/lib/tldr/planner.rb +15 -56
- data/lib/tldr/rake.rb +42 -3
- data/lib/tldr/reporters/default.rb +1 -0
- data/lib/tldr/reporters/icon_provider.rb +8 -0
- data/lib/tldr/runner.rb +1 -1
- data/lib/tldr/strategizer.rb +14 -4
- data/lib/tldr/value/config.rb +74 -42
- data/lib/tldr/value/test_group.rb +2 -2
- data/lib/tldr/version.rb +1 -1
- data/lib/tldr.rb +4 -0
- metadata +5 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4be82f4551acdf8d4312b512a1c12d2a857cb1d1420b4eebc6d7a74632eecd19
|
4
|
+
data.tar.gz: 8f10cecb4d4701fb24f84c36ec78f6b91fe4b381bcf6c86f02f2743f93b61a1f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8262b1e6ee4fd4154d9c46d883d1d49b76026a3752b5d26a4ea520fa8b0c0b786019cb3c770b319bd22130c1f194883d02cefbb38b9f171a4571b91ab1793d5f
|
7
|
+
data.tar.gz: 125b89ee409f0080a61ceb00232464c455213223883ce10502742a90a825c0a208ed625e5608ecd585a26a900bc49dd8d6a1b49202a88684386c455d9eb313a0
|
data/CHANGELOG.md
CHANGED
@@ -1,4 +1,20 @@
|
|
1
|
-
## [
|
1
|
+
## [0.6.0]
|
2
|
+
|
3
|
+
* When `dont_run_these_in_parallel!` and `run_these_together!` are called from a
|
4
|
+
super class, gather subclasses' methods as well when the method is `nil`
|
5
|
+
* Stop shelling out to `tldr` from our Rake task. Rescue `SystemExit` instead
|
6
|
+
* Rename `Config#helper` to `Config#helper_paths`, which YAML config keys
|
7
|
+
* Print Ruby warnings by default (disable with --no-warnings)
|
8
|
+
|
9
|
+
## [0.5.0]
|
10
|
+
|
11
|
+
* Define your own Rake tasks with `TLDR::Task` and pass in a custom configuration
|
12
|
+
* Any tests with `--prepend` AND marked thread-unsafe with `dont_run_these_in_parallel`
|
13
|
+
will be run BEFORE the parallel tests start running. This way if you're working
|
14
|
+
on a non-parallelizable test, running `tldr` will automatically run it first
|
15
|
+
thing
|
16
|
+
* Stop printing `--seed` in run commands, since it can be confusing to discover
|
17
|
+
that will disable `--parallel`. Instead, print the seed option beneath
|
2
18
|
|
3
19
|
## [0.4.0]
|
4
20
|
|
data/README.md
CHANGED
@@ -23,8 +23,8 @@ suite.
|
|
23
23
|
|
24
24
|
Some stuff you might like:
|
25
25
|
|
26
|
-
* A CLI that can
|
27
|
-
by names or patterns (e.g. `--name test_fail,test_error --name "/_\d/"`)
|
26
|
+
* A CLI that can specify tests by line number(s) (e.g. `foo.rb:5 bar.rb:3:10`)
|
27
|
+
and by names or patterns (e.g. `--name test_fail,test_error --name "/_\d/"`)
|
28
28
|
* Everything is **parallel by default**, and seems pretty darn fast; TLDR
|
29
29
|
also provides [several escape hatches to sequester tests that aren't thread-safe](#parallel-by-default-is-nice-in-theory-but-half-my-tests-are-failing-wat)
|
30
30
|
* Surprisingly delightful color diff output when two things fail to equal one
|
@@ -112,8 +112,8 @@ Usage: tldr [options] some_tests/**/*.rb some/path.rb:13 ...
|
|
112
112
|
-n, --name PATTERN One or more names or /patterns/ of tests to run (like: foo_test, /test_foo.*/, Foo#foo_test)
|
113
113
|
--exclude-name PATTERN One or more names or /patterns/ NOT to run
|
114
114
|
--exclude-path PATH One or more paths NOT to run (like: foo.rb, "test/bar/**", baz.rb:3)
|
115
|
-
--helper
|
116
|
-
--no-helper Don't
|
115
|
+
--helper PATH One or more paths to a helper that is required before any tests (Default: "test/helper.rb")
|
116
|
+
--no-helper Don't require any test helpers
|
117
117
|
--prepend PATH Prepend one or more paths to run before the rest (Default: most recently modified test)
|
118
118
|
--no-prepend Don't prepend any tests before the rest of the suite
|
119
119
|
-l, --load-path PATH Add one or more paths to the $LOAD_PATH (Default: ["test"])
|
@@ -122,6 +122,7 @@ Usage: tldr [options] some_tests/**/*.rb some/path.rb:13 ...
|
|
122
122
|
--no-dotfile Disable loading .tldr.yml dotfile
|
123
123
|
--no-emoji Disable emoji in the output
|
124
124
|
-v, --verbose Print stack traces for errors
|
125
|
+
--[no-]warnings Print Ruby warnings (Default: true)
|
125
126
|
--comment COMMENT No-op comment, used internally for multi-line execution instructions
|
126
127
|
```
|
127
128
|
|
@@ -181,6 +182,27 @@ You could then run the task with:
|
|
181
182
|
$ TLDR_OPTS="--no-parallel" bundle exec rake tldr
|
182
183
|
```
|
183
184
|
|
185
|
+
One reason you'd want to invoke TLDR with Rake is because you have multiple
|
186
|
+
test suites that you want to be able to conveniently run separately ([this
|
187
|
+
talk](https://blog.testdouble.com/talks/2014-05-25-breaking-up-with-your-test-suite/)
|
188
|
+
discussed a few reasons why this can be useful).
|
189
|
+
|
190
|
+
To create a custom TLDR Rake test, just instantiate `TLDR::Task` like this:
|
191
|
+
|
192
|
+
```ruby
|
193
|
+
require "tldr/rake"
|
194
|
+
|
195
|
+
TLDR::Task.new(name: :safe_tests, config: TLDR::Config.new(
|
196
|
+
paths: FileList["safe/**/*_test.rb"],
|
197
|
+
helper_paths: ["safe/helper.rb"],
|
198
|
+
load_paths: ["lib", "safe"]
|
199
|
+
))
|
200
|
+
```
|
201
|
+
|
202
|
+
The above will create a second Rake task named `safe_tests` running a different
|
203
|
+
set of tests than the default `tldr` task. Here's [an
|
204
|
+
example](/example/b/Rakefile).
|
205
|
+
|
184
206
|
### Running tests without the CLI
|
185
207
|
|
186
208
|
If you'd rather use TLDR by running Ruby files instead of the `tldr` CLI
|
@@ -207,7 +229,7 @@ $ ruby test/some_test.rb
|
|
207
229
|
|
208
230
|
To maximize control and to avoid running code accidentally (and _unlike_ the
|
209
231
|
`tldr` CLI), running `at_exit!` will not set default values to the `paths`,
|
210
|
-
`helper`, `load_paths`, and `
|
232
|
+
`helper`, `load_paths`, and `prepend_paths` config properties. You'll have to
|
211
233
|
pass any values you want to set on a [Config object](/lib/tldr/value/config.rb)
|
212
234
|
and pass it to `at_exit!`.
|
213
235
|
|
data/lib/tldr/argv_parser.rb
CHANGED
@@ -4,11 +4,7 @@ class TLDR
|
|
4
4
|
class ArgvParser
|
5
5
|
PATTERN_FRIENDLY_SPLITTER = /,(?=(?:[^\/]*\/[^\/]*\/)*[^\/]*$)/
|
6
6
|
|
7
|
-
def parse(args)
|
8
|
-
options = {
|
9
|
-
cli_mode: true
|
10
|
-
}
|
11
|
-
|
7
|
+
def parse(args, options = {cli_defaults: true})
|
12
8
|
OptionParser.new do |opts|
|
13
9
|
opts.banner = "Usage: tldr [options] some_tests/**/*.rb some/path.rb:13 ..."
|
14
10
|
|
@@ -39,17 +35,18 @@ class TLDR
|
|
39
35
|
options[:exclude_paths] += path
|
40
36
|
end
|
41
37
|
|
42
|
-
opts.on "#{CONFLAGS[:
|
43
|
-
options[:
|
38
|
+
opts.on "#{CONFLAGS[:helper_paths]} PATH", Array, "One or more paths to a helper that is required before any tests (Default: \"test/helper.rb\")" do |path|
|
39
|
+
options[:helper_paths] ||= []
|
40
|
+
options[:helper_paths] += path
|
44
41
|
end
|
45
42
|
|
46
|
-
opts.on CONFLAGS[:no_helper], "Don't
|
43
|
+
opts.on CONFLAGS[:no_helper], "Don't require any test helpers" do
|
47
44
|
options[:no_helper] = true
|
48
45
|
end
|
49
46
|
|
50
|
-
opts.on "#{CONFLAGS[:
|
51
|
-
options[:
|
52
|
-
options[:
|
47
|
+
opts.on "#{CONFLAGS[:prepend_paths]} PATH", Array, "Prepend one or more paths to run before the rest (Default: most recently modified test)" do |prepend|
|
48
|
+
options[:prepend_paths] ||= []
|
49
|
+
options[:prepend_paths] += prepend
|
53
50
|
end
|
54
51
|
|
55
52
|
opts.on CONFLAGS[:no_prepend], "Don't prepend any tests before the rest of the suite" do
|
@@ -81,6 +78,10 @@ class TLDR
|
|
81
78
|
options[:verbose] = verbose
|
82
79
|
end
|
83
80
|
|
81
|
+
opts.on CONFLAGS[:warnings], "Print Ruby warnings (Default: true)" do |warnings|
|
82
|
+
options[:warnings] = warnings
|
83
|
+
end
|
84
|
+
|
84
85
|
opts.on "--comment COMMENT", String, "No-op comment, used internally for multi-line execution instructions" do
|
85
86
|
# See "--comment" in lib/tldr/reporters/default.rb for an example of how this is used internally
|
86
87
|
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
class TLDR
|
2
|
+
module ClassUtil
|
3
|
+
def self.gather_descendants root_klass
|
4
|
+
root_klass.subclasses + root_klass.subclasses.flat_map { |subklass|
|
5
|
+
gather_descendants subklass
|
6
|
+
}
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.gather_tests klass
|
10
|
+
klass.instance_methods.grep(/^test_/).sort.map { |method|
|
11
|
+
Test.new klass, method
|
12
|
+
}
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
data/lib/tldr/parallelizer.rb
CHANGED
@@ -8,12 +8,18 @@ class TLDR
|
|
8
8
|
)
|
9
9
|
end
|
10
10
|
|
11
|
-
def parallelize all_tests,
|
12
|
-
return run_in_sequence(all_tests, &blk) if all_tests.size < 2 || !parallel
|
11
|
+
def parallelize all_tests, config, &blk
|
12
|
+
return run_in_sequence(all_tests, &blk) if all_tests.size < 2 || !config.parallel
|
13
13
|
|
14
|
-
strategy = @strategizer.strategize
|
14
|
+
strategy = @strategizer.strategize(
|
15
|
+
all_tests,
|
16
|
+
GROUPED_TESTS,
|
17
|
+
THREAD_UNSAFE_TESTS,
|
18
|
+
(config.no_prepend ? [] : config.prepend_paths)
|
19
|
+
)
|
15
20
|
|
16
|
-
|
21
|
+
run_in_sequence(strategy.prepend_thread_unsafe_tests, &blk) +
|
22
|
+
run_in_parallel(strategy.parallel_tests_and_groups, &blk) +
|
17
23
|
run_in_sequence(strategy.thread_unsafe_tests, &blk)
|
18
24
|
end
|
19
25
|
|
@@ -0,0 +1,43 @@
|
|
1
|
+
class TLDR
|
2
|
+
module PathUtil
|
3
|
+
def self.expand_paths path_strings, globs: true
|
4
|
+
path_strings = expand_globs path_strings if globs
|
5
|
+
|
6
|
+
path_strings.flat_map { |path_string|
|
7
|
+
File.directory?(path_string) ? Dir["#{path_string}/**/*.rb"] : path_string
|
8
|
+
}.flat_map { |path_string|
|
9
|
+
absolute_path = File.expand_path(path_string.gsub(/:.*$/, ""), Dir.pwd)
|
10
|
+
line_numbers = path_string.scan(/:(\d+)/).flatten.map(&:to_i)
|
11
|
+
|
12
|
+
if line_numbers.any?
|
13
|
+
line_numbers.map { |line_number| Location.new absolute_path, line_number }
|
14
|
+
else
|
15
|
+
[Location.new(absolute_path, nil)]
|
16
|
+
end
|
17
|
+
}.uniq
|
18
|
+
end
|
19
|
+
|
20
|
+
# Because search paths to TLDR can include line numbers (e.g. a.rb:4), we
|
21
|
+
# can't just pass everything to Dir.glob. Instead, we have to check whether
|
22
|
+
# a user-provided search path looks like a glob, and if so, expand it
|
23
|
+
#
|
24
|
+
# Globby characters specified here:
|
25
|
+
# https://ruby-doc.org/3.2.2/Dir.html#method-c-glob
|
26
|
+
def self.expand_globs search_paths
|
27
|
+
search_paths.flat_map { |search_path|
|
28
|
+
if search_path.match?(/[*?\[\]{}]/)
|
29
|
+
raise Error, "Can't combine globs and line numbers in: #{search_path}" if search_path.match?(/:(\d+)$/)
|
30
|
+
Dir[search_path]
|
31
|
+
else
|
32
|
+
search_path
|
33
|
+
end
|
34
|
+
}
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.locations_include_test? locations, test
|
38
|
+
locations.any? { |location|
|
39
|
+
location.file == test.file && (location.line.nil? || test.covers_line?(location.line))
|
40
|
+
}
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
data/lib/tldr/planner.rb
CHANGED
@@ -3,7 +3,8 @@ require "pathname"
|
|
3
3
|
class TLDR
|
4
4
|
class Planner
|
5
5
|
def plan config
|
6
|
-
|
6
|
+
$VERBOSE = config.warnings
|
7
|
+
search_locations = PathUtil.expand_paths config.paths, globs: false
|
7
8
|
|
8
9
|
prepend_load_paths config
|
9
10
|
require_test_helper config
|
@@ -32,34 +33,17 @@ class TLDR
|
|
32
33
|
|
33
34
|
private
|
34
35
|
|
35
|
-
def expand_search_locations path_strings
|
36
|
-
path_strings.flat_map { |path_string|
|
37
|
-
File.directory?(path_string) ? Dir["#{path_string}/**/*.rb"] : path_string
|
38
|
-
}.flat_map { |path_string|
|
39
|
-
absolute_path = File.expand_path(path_string.gsub(/:.*$/, ""), Dir.pwd)
|
40
|
-
line_numbers = path_string.scan(/:(\d+)/).flatten.map(&:to_i)
|
41
|
-
|
42
|
-
if line_numbers.any?
|
43
|
-
line_numbers.map { |line_number| Location.new absolute_path, line_number }
|
44
|
-
else
|
45
|
-
[Location.new(absolute_path, nil)]
|
46
|
-
end
|
47
|
-
}.uniq
|
48
|
-
end
|
49
|
-
|
50
36
|
def gather_tests
|
51
|
-
gather_descendants(TLDR).flat_map { |subklass|
|
52
|
-
|
53
|
-
Test.new subklass, method
|
54
|
-
}
|
37
|
+
ClassUtil.gather_descendants(TLDR).flat_map { |subklass|
|
38
|
+
ClassUtil.gather_tests(subklass)
|
55
39
|
}
|
56
40
|
end
|
57
41
|
|
58
42
|
def prepend tests, config
|
59
43
|
return tests if config.no_prepend
|
60
|
-
prepended_locations =
|
44
|
+
prepended_locations = PathUtil.expand_paths config.prepend_paths
|
61
45
|
prepended, rest = tests.partition { |test|
|
62
|
-
locations_include_test? prepended_locations, test
|
46
|
+
PathUtil.locations_include_test? prepended_locations, test
|
63
47
|
}
|
64
48
|
prepended + rest
|
65
49
|
end
|
@@ -69,11 +53,11 @@ class TLDR
|
|
69
53
|
end
|
70
54
|
|
71
55
|
def exclude_by_path tests, exclude_paths
|
72
|
-
excluded_locations =
|
56
|
+
excluded_locations = PathUtil.expand_paths exclude_paths
|
73
57
|
return tests if excluded_locations.empty?
|
74
58
|
|
75
59
|
tests.reject { |test|
|
76
|
-
locations_include_test? excluded_locations, test
|
60
|
+
PathUtil.locations_include_test? excluded_locations, test
|
77
61
|
}
|
78
62
|
end
|
79
63
|
|
@@ -94,7 +78,7 @@ class TLDR
|
|
94
78
|
return tests if line_specific_locations.empty?
|
95
79
|
|
96
80
|
tests.select { |test|
|
97
|
-
locations_include_test? line_specific_locations, test
|
81
|
+
PathUtil.locations_include_test? line_specific_locations, test
|
98
82
|
}
|
99
83
|
end
|
100
84
|
|
@@ -117,8 +101,12 @@ class TLDR
|
|
117
101
|
end
|
118
102
|
|
119
103
|
def require_test_helper config
|
120
|
-
return if config.no_helper || config.
|
121
|
-
|
104
|
+
return if config.no_helper || config.helper_paths.empty?
|
105
|
+
PathUtil.expand_paths(config.helper_paths).map(&:file).uniq.each do |helper_file|
|
106
|
+
next unless File.exist?(helper_file)
|
107
|
+
|
108
|
+
require helper_file
|
109
|
+
end
|
122
110
|
end
|
123
111
|
|
124
112
|
def require_tests search_locations
|
@@ -127,35 +115,6 @@ class TLDR
|
|
127
115
|
end
|
128
116
|
end
|
129
117
|
|
130
|
-
def gather_descendants root_klass
|
131
|
-
root_klass.subclasses + root_klass.subclasses.flat_map { |subklass|
|
132
|
-
gather_descendants subklass
|
133
|
-
}
|
134
|
-
end
|
135
|
-
|
136
|
-
def locations_include_test? locations, test
|
137
|
-
locations.any? { |location|
|
138
|
-
location.file == test.file && (location.line.nil? || test.covers_line?(location.line))
|
139
|
-
}
|
140
|
-
end
|
141
|
-
|
142
|
-
# Because search paths to TLDR can include line numbers (e.g. a.rb:4), we
|
143
|
-
# can't just pass everything to Dir.glob. Instead, we have to check whether
|
144
|
-
# a user-provided search path looks like a glob, and if so, expand it
|
145
|
-
#
|
146
|
-
# Globby characters specified here:
|
147
|
-
# https://ruby-doc.org/3.2.2/Dir.html#method-c-glob
|
148
|
-
def expand_globs search_paths
|
149
|
-
search_paths.flat_map { |search_path|
|
150
|
-
if search_path.match?(/[*?\[\]{}]/)
|
151
|
-
raise Error, "Can't combine globs and line numbers in: #{search_path}" if search_path.match?(/:(\d+)$/)
|
152
|
-
Dir[search_path]
|
153
|
-
else
|
154
|
-
search_path
|
155
|
-
end
|
156
|
-
}
|
157
|
-
end
|
158
|
-
|
159
118
|
def expand_names_with_patterns names
|
160
119
|
names.map { |name|
|
161
120
|
if name.is_a?(String) && name =~ /^\/(.*)\/$/
|
data/lib/tldr/rake.rb
CHANGED
@@ -1,4 +1,43 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
1
|
+
require "rake"
|
2
|
+
require "shellwords"
|
3
|
+
|
4
|
+
require "tldr"
|
5
|
+
|
6
|
+
class TLDR
|
7
|
+
class Task
|
8
|
+
include Rake::DSL
|
9
|
+
|
10
|
+
def initialize(name: "tldr", config: Config.new)
|
11
|
+
define name, config
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def define name, task_config
|
17
|
+
desc "Run #{name} tests (use TLDR_OPTS or .tldr.yml to configure)"
|
18
|
+
task name do
|
19
|
+
argv = Shellwords.shellwords(merge_env_opts(task_config).to_full_args)
|
20
|
+
begin
|
21
|
+
TLDR::Run.cli(argv)
|
22
|
+
rescue SystemExit => e
|
23
|
+
fail "TLDR task #{name} failed with status #{e.status}" unless e.status == 0
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def merge_env_opts task_config
|
29
|
+
if ENV["TLDR_OPTS"]
|
30
|
+
env_argv = Shellwords.shellwords(ENV["TLDR_OPTS"])
|
31
|
+
opts_config = ArgvParser.new.parse(env_argv, {
|
32
|
+
config_intended_for_merge_only: true
|
33
|
+
})
|
34
|
+
task_config.merge(opts_config)
|
35
|
+
else
|
36
|
+
task_config
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
4
40
|
end
|
41
|
+
|
42
|
+
# Create the default tldr task for users
|
43
|
+
TLDR::Task.new
|
data/lib/tldr/runner.rb
CHANGED
@@ -34,7 +34,7 @@ class TLDR
|
|
34
34
|
end
|
35
35
|
}
|
36
36
|
|
37
|
-
results = @parallelizer.parallelize(plan.tests, config
|
37
|
+
results = @parallelizer.parallelize(plan.tests, config) { |test|
|
38
38
|
e = nil
|
39
39
|
start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC, :microsecond)
|
40
40
|
wip_test = WIPTest.new test, start_time
|
data/lib/tldr/strategizer.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
class TLDR
|
2
2
|
class Strategizer
|
3
|
-
Strategy = Struct.new :parallel_tests_and_groups, :thread_unsafe_tests
|
3
|
+
Strategy = Struct.new :prepend_thread_unsafe_tests, :parallel_tests_and_groups, :thread_unsafe_tests
|
4
4
|
|
5
5
|
# Combine all discovered test methods with any methods grouped by run_these_together!
|
6
6
|
#
|
@@ -8,13 +8,13 @@ class TLDR
|
|
8
8
|
# - Map over tests to build out groups in order to retain shuffle order
|
9
9
|
# (group will run in position of first test in the group)
|
10
10
|
# - If a test is in multiple groups, only run it once
|
11
|
-
def strategize all_tests, run_these_together_groups, thread_unsafe_test_groups
|
11
|
+
def strategize all_tests, run_these_together_groups, thread_unsafe_test_groups, prepend_paths
|
12
12
|
thread_unsafe_tests, thread_safe_tests = partition_unsafe(all_tests, thread_unsafe_test_groups)
|
13
|
+
prepend_thread_unsafe_tests, thread_unsafe_tests = partition_prepend(thread_unsafe_tests, prepend_paths)
|
13
14
|
|
14
15
|
grouped_tests = prepare_run_together_groups run_these_together_groups, thread_safe_tests, thread_unsafe_tests
|
15
16
|
already_included_groups = []
|
16
|
-
|
17
|
-
Strategy.new thread_safe_tests.map { |test|
|
17
|
+
Strategy.new prepend_thread_unsafe_tests, thread_safe_tests.map { |test|
|
18
18
|
if (group = grouped_tests.find { |group| group.tests.include? test })
|
19
19
|
if already_included_groups.include? group
|
20
20
|
next
|
@@ -39,6 +39,16 @@ class TLDR
|
|
39
39
|
}
|
40
40
|
end
|
41
41
|
|
42
|
+
# Sadly duplicative with Planner.rb, necessitating the extraction of PathUtil
|
43
|
+
# Suboptimal, but we do indeed need to do this work in two places ¯\_(ツ)_/¯
|
44
|
+
def partition_prepend thread_unsafe_tests, prepend_paths
|
45
|
+
locations = PathUtil.expand_paths prepend_paths
|
46
|
+
|
47
|
+
thread_unsafe_tests.partition { |test|
|
48
|
+
PathUtil.locations_include_test? locations, test
|
49
|
+
}
|
50
|
+
end
|
51
|
+
|
42
52
|
def prepare_run_together_groups run_these_together_groups, thread_safe_tests, thread_unsafe_tests
|
43
53
|
grouped_tests = run_these_together_groups.map(&:dup)
|
44
54
|
|
data/lib/tldr/value/config.rb
CHANGED
@@ -4,40 +4,53 @@ class TLDR
|
|
4
4
|
no_helper: "--no-helper",
|
5
5
|
verbose: "--verbose",
|
6
6
|
reporter: "--reporter",
|
7
|
-
|
7
|
+
helper_paths: "--helper",
|
8
8
|
load_paths: "--load-path",
|
9
9
|
parallel: "--[no-]parallel",
|
10
10
|
names: "--name",
|
11
11
|
fail_fast: "--fail-fast",
|
12
12
|
no_emoji: "--no-emoji",
|
13
|
-
|
13
|
+
prepend_paths: "--prepend",
|
14
14
|
no_prepend: "--no-prepend",
|
15
15
|
exclude_paths: "--exclude-path",
|
16
16
|
exclude_names: "--exclude-name",
|
17
17
|
base_path: "--base-path",
|
18
18
|
no_dotfile: "--no-dotfile",
|
19
|
+
warnings: "--[no-]warnings",
|
19
20
|
paths: nil
|
20
21
|
}.freeze
|
21
22
|
|
22
|
-
PATH_FLAGS = [:paths, :
|
23
|
+
PATH_FLAGS = [:paths, :helper_paths, :load_paths, :prepend_paths, :exclude_paths].freeze
|
23
24
|
MOST_RECENTLY_MODIFIED_TAG = "MOST_RECENTLY_MODIFIED".freeze
|
24
|
-
|
25
|
-
|
26
|
-
:
|
27
|
-
:
|
28
|
-
:no_dotfile,
|
29
|
-
|
25
|
+
CONFIG_ATTRIBUTES = [
|
26
|
+
:paths, :seed, :no_helper, :verbose, :reporter,
|
27
|
+
:helper_paths, :load_paths, :parallel, :names, :fail_fast, :no_emoji,
|
28
|
+
:prepend_paths, :no_prepend, :exclude_paths, :exclude_names, :base_path,
|
29
|
+
:no_dotfile, :warnings,
|
30
|
+
# Internal properties
|
31
|
+
:config_intended_for_merge_only, :seed_set_intentionally, :cli_defaults
|
32
|
+
].freeze
|
33
|
+
|
34
|
+
Config = Struct.new(*CONFIG_ATTRIBUTES, keyword_init: true) do
|
30
35
|
def initialize(**args)
|
31
|
-
|
32
|
-
|
36
|
+
unless args[:config_intended_for_merge_only]
|
37
|
+
change_working_directory_because_i_am_bad_and_i_should_feel_bad!(args[:base_path])
|
38
|
+
args = merge_dotfile_args(args) unless args[:no_dotfile]
|
39
|
+
end
|
40
|
+
args = undefault_parallel_if_seed_set(args)
|
41
|
+
unless args[:config_intended_for_merge_only]
|
42
|
+
args = merge_defaults(args)
|
43
|
+
end
|
33
44
|
|
34
|
-
super(**
|
45
|
+
super(**args)
|
35
46
|
end
|
36
47
|
|
37
|
-
#
|
38
|
-
undef_method :
|
48
|
+
# These are for internal tracking and resolved at initialization-time
|
49
|
+
undef_method :config_intended_for_merge_only=, :seed_set_intentionally=,
|
50
|
+
# These must be set when the Config is first initialized
|
51
|
+
:cli_defaults=, :no_dotfile=, :base_path=
|
39
52
|
|
40
|
-
def self.build_defaults
|
53
|
+
def self.build_defaults cli_defaults: true
|
41
54
|
common = {
|
42
55
|
seed: rand(10_000),
|
43
56
|
no_helper: false,
|
@@ -50,62 +63,71 @@ class TLDR
|
|
50
63
|
no_prepend: false,
|
51
64
|
exclude_paths: [],
|
52
65
|
exclude_names: [],
|
53
|
-
base_path: nil
|
66
|
+
base_path: nil,
|
67
|
+
warnings: true
|
54
68
|
}
|
55
69
|
|
56
|
-
if
|
70
|
+
if cli_defaults
|
57
71
|
common.merge(
|
58
72
|
paths: Dir["test/**/*_test.rb", "test/**/test_*.rb"],
|
59
|
-
|
73
|
+
helper_paths: ["test/helper.rb"],
|
60
74
|
load_paths: ["test"],
|
61
|
-
|
75
|
+
prepend_paths: [MOST_RECENTLY_MODIFIED_TAG]
|
62
76
|
)
|
63
77
|
else
|
64
78
|
common.merge(
|
65
79
|
paths: [],
|
66
|
-
|
80
|
+
helper_paths: [],
|
67
81
|
load_paths: [],
|
68
|
-
|
82
|
+
prepend_paths: []
|
69
83
|
)
|
70
84
|
end
|
71
85
|
end
|
72
86
|
|
73
|
-
def
|
74
|
-
|
75
|
-
|
76
|
-
|
87
|
+
def undefault_parallel_if_seed_set args
|
88
|
+
args.merge(
|
89
|
+
seed_set_intentionally: !args[:seed].nil?,
|
90
|
+
parallel: (args[:parallel].nil? ? args[:seed].nil? : args[:parallel])
|
91
|
+
)
|
92
|
+
end
|
77
93
|
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
merged_args[:parallel] = merged_args[:seed].nil?
|
82
|
-
end
|
94
|
+
def merge_defaults user_args
|
95
|
+
merged_args = user_args.dup
|
96
|
+
defaults = Config.build_defaults(cli_defaults: merged_args[:cli_defaults])
|
83
97
|
|
84
98
|
# Arrays
|
85
|
-
[:paths, :load_paths, :names, :
|
99
|
+
[:paths, :helper_paths, :load_paths, :names, :prepend_paths, :exclude_paths, :exclude_names].each do |key|
|
86
100
|
merged_args[key] = defaults[key] if merged_args[key].nil? || merged_args[key].empty?
|
87
101
|
end
|
88
102
|
|
89
103
|
# Booleans
|
90
|
-
[:no_helper, :verbose, :fail_fast, :no_emoji, :no_prepend].each do |key|
|
104
|
+
[:no_helper, :verbose, :fail_fast, :no_emoji, :no_prepend, :warnings].each do |key|
|
91
105
|
merged_args[key] = defaults[key] if merged_args[key].nil?
|
92
106
|
end
|
93
107
|
|
94
108
|
# Values
|
95
|
-
[:seed, :reporter
|
109
|
+
[:seed, :reporter].each do |key|
|
96
110
|
merged_args[key] ||= defaults[key]
|
97
111
|
end
|
98
112
|
|
99
113
|
merged_args
|
100
114
|
end
|
101
115
|
|
116
|
+
def merge other
|
117
|
+
this_config = to_h
|
118
|
+
kwargs = this_config.merge(
|
119
|
+
other.to_h.compact.except(:config_intended_for_merge_only)
|
120
|
+
)
|
121
|
+
Config.new(**kwargs)
|
122
|
+
end
|
123
|
+
|
102
124
|
# We needed this hook (to be called by the planner), because we can't know
|
103
125
|
# the default prepend location until we have all the resolved test paths,
|
104
126
|
# so we have to mutate it after the fact.
|
105
127
|
def update_after_gathering_tests! tests
|
106
|
-
return unless
|
128
|
+
return unless prepend_paths.include?(MOST_RECENTLY_MODIFIED_TAG)
|
107
129
|
|
108
|
-
self.
|
130
|
+
self.prepend_paths = prepend_paths.map { |path|
|
109
131
|
if path == MOST_RECENTLY_MODIFIED_TAG
|
110
132
|
most_recently_modified_test_file tests
|
111
133
|
else
|
@@ -115,12 +137,17 @@ class TLDR
|
|
115
137
|
end
|
116
138
|
|
117
139
|
def to_full_args(exclude: [])
|
118
|
-
to_cli_argv(
|
140
|
+
to_cli_argv(
|
141
|
+
CONFLAGS.keys -
|
142
|
+
exclude - [
|
143
|
+
(:seed unless seed_set_intentionally)
|
144
|
+
]
|
145
|
+
).join(" ")
|
119
146
|
end
|
120
147
|
|
121
148
|
def to_single_path_args(path)
|
122
149
|
argv = to_cli_argv(CONFLAGS.keys - [
|
123
|
-
:seed, :parallel, :names, :fail_fast, :paths, :
|
150
|
+
:seed, :parallel, :names, :fail_fast, :paths, :prepend_paths,
|
124
151
|
:no_prepend, :exclude_paths
|
125
152
|
])
|
126
153
|
|
@@ -129,21 +156,21 @@ class TLDR
|
|
129
156
|
|
130
157
|
private
|
131
158
|
|
132
|
-
def to_cli_argv
|
133
|
-
defaults = Config.build_defaults(
|
159
|
+
def to_cli_argv options = CONFLAGS.keys
|
160
|
+
defaults = Config.build_defaults(cli_defaults: true)
|
134
161
|
options.map { |key|
|
135
162
|
flag = CONFLAGS[key]
|
136
163
|
|
137
164
|
# Special cases
|
138
|
-
if key == :
|
139
|
-
if
|
165
|
+
if key == :prepend_paths
|
166
|
+
if prepend_paths.map { |s| stringify(key, s) }.sort == paths.map { |s| stringify(:paths, s) }.sort
|
140
167
|
# Don't print prepended tests if they're the same as the test paths
|
141
168
|
next
|
142
169
|
elsif no_prepend
|
143
170
|
# Don't print prepended tests if they're disabled
|
144
171
|
next
|
145
172
|
end
|
146
|
-
elsif key == :
|
173
|
+
elsif key == :helper_paths && no_helper
|
147
174
|
# Don't print the helper if it's disabled
|
148
175
|
next
|
149
176
|
elsif key == :parallel
|
@@ -153,6 +180,8 @@ class TLDR
|
|
153
180
|
"--parallel"
|
154
181
|
end
|
155
182
|
next val
|
183
|
+
elsif key == :warnings && defaults[:warnings] != self[:warnings]
|
184
|
+
next warnings ? "--warnings" : "--no-warnings"
|
156
185
|
end
|
157
186
|
|
158
187
|
if defaults[key] == self[key]
|
@@ -208,6 +237,9 @@ class TLDR
|
|
208
237
|
if dotfile_args.key?(:reporter)
|
209
238
|
dotfile_args[:reporter] = Kernel.const_get(dotfile_args[:reporter])
|
210
239
|
end
|
240
|
+
if (invalid_args = dotfile_args.except(*CONFIG_ATTRIBUTES)).any?
|
241
|
+
raise Error, "Invalid keys in .tldr.yml file: #{invalid_args.keys.join(", ")}"
|
242
|
+
end
|
211
243
|
|
212
244
|
dotfile_args.merge(args)
|
213
245
|
end
|
@@ -6,8 +6,8 @@ class TLDR
|
|
6
6
|
@tests ||= configuration.flat_map { |(klass, method)|
|
7
7
|
klass = Kernel.const_get(klass) if klass.is_a? String
|
8
8
|
if method.nil?
|
9
|
-
klass.
|
10
|
-
|
9
|
+
([klass] + ClassUtil.gather_descendants(klass)).flat_map { |klass|
|
10
|
+
ClassUtil.gather_tests(klass)
|
11
11
|
}
|
12
12
|
else
|
13
13
|
Test.new klass, method
|
data/lib/tldr/version.rb
CHANGED
data/lib/tldr.rb
CHANGED
@@ -3,9 +3,11 @@ require "concurrent-ruby"
|
|
3
3
|
require_relative "tldr/argv_parser"
|
4
4
|
require_relative "tldr/assertions"
|
5
5
|
require_relative "tldr/backtrace_filter"
|
6
|
+
require_relative "tldr/class_util"
|
6
7
|
require_relative "tldr/error"
|
7
8
|
require_relative "tldr/parallel_controls"
|
8
9
|
require_relative "tldr/parallelizer"
|
10
|
+
require_relative "tldr/path_util"
|
9
11
|
require_relative "tldr/planner"
|
10
12
|
require_relative "tldr/reporters"
|
11
13
|
require_relative "tldr/runner"
|
@@ -39,6 +41,8 @@ class TLDR
|
|
39
41
|
def self.at_exit! config = Config.new
|
40
42
|
# Ignore at_exit when running tldr CLI, since that will run any tests
|
41
43
|
return if $PROGRAM_NAME.end_with? "tldr"
|
44
|
+
# Also ignore if we're running from within our rake task
|
45
|
+
return if caller.any? { |line| line.include? "lib/tldr/rake.rb" }
|
42
46
|
# Ignore at_exit when we've already registered an at_exit hook
|
43
47
|
return if @@at_exit_registered
|
44
48
|
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: tldr
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.6.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Justin Searls
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: exe
|
11
11
|
cert_chain: []
|
12
|
-
date: 2023-09-
|
12
|
+
date: 2023-09-29 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: super_diff
|
@@ -59,9 +59,11 @@ files:
|
|
59
59
|
- lib/tldr/assertions.rb
|
60
60
|
- lib/tldr/assertions/minitest_compatibility.rb
|
61
61
|
- lib/tldr/backtrace_filter.rb
|
62
|
+
- lib/tldr/class_util.rb
|
62
63
|
- lib/tldr/error.rb
|
63
64
|
- lib/tldr/parallel_controls.rb
|
64
65
|
- lib/tldr/parallelizer.rb
|
66
|
+
- lib/tldr/path_util.rb
|
65
67
|
- lib/tldr/planner.rb
|
66
68
|
- lib/tldr/rake.rb
|
67
69
|
- lib/tldr/reporters.rb
|
@@ -105,7 +107,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
105
107
|
- !ruby/object:Gem::Version
|
106
108
|
version: '0'
|
107
109
|
requirements: []
|
108
|
-
rubygems_version: 3.4.
|
110
|
+
rubygems_version: 3.4.6
|
109
111
|
signing_key:
|
110
112
|
specification_version: 4
|
111
113
|
summary: TLDR will run your tests, but only for 1.8 seconds.
|