tldr 0.4.0 → 0.6.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 +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.
|