tldr 0.7.0 → 0.9.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 +11 -0
- data/README.md +33 -4
- data/lib/tldr/argv_parser.rb +17 -5
- data/lib/tldr/assertions/minitest_compatibility.rb +1 -1
- data/lib/tldr/assertions.rb +3 -3
- data/lib/tldr/backtrace_filter.rb +4 -4
- data/lib/tldr/class_util.rb +2 -2
- data/lib/tldr/path_util.rb +2 -2
- data/lib/tldr/planner.rb +15 -15
- data/lib/tldr/rake.rb +1 -1
- data/lib/tldr/reporters/base.rb +1 -1
- data/lib/tldr/reporters/default.rb +47 -34
- data/lib/tldr/runner.rb +16 -16
- data/lib/tldr/strategizer.rb +8 -8
- data/lib/tldr/value/config.rb +28 -14
- data/lib/tldr/value/location.rb +1 -1
- data/lib/tldr/value/plan.rb +1 -1
- data/lib/tldr/value/test.rb +3 -3
- data/lib/tldr/value/test_group.rb +3 -3
- data/lib/tldr/value/test_result.rb +1 -1
- data/lib/tldr/value/wip_test.rb +1 -1
- data/lib/tldr/version.rb +1 -1
- data/lib/tldr/watcher.rb +32 -0
- data/lib/tldr.rb +11 -6
- 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: c9ae325a1d7b5c16b255bce28975b4e9c08bc23291932a02d91d02d46c247655
|
4
|
+
data.tar.gz: 0e92807c78187999ea26dba8f3ff48aa8ec6442b8884def3b7fc6b58db4127f0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 79a8a3c45bc03731c80e5d7b00103d69efe4a1185c758fdecde1451864677af433126634a717dd912d7032556562cbe3894b1e7db75ab811412c1cc9097a457e
|
7
|
+
data.tar.gz: f27b99c4502b3a803d9c0507a1a9888715e8d203e1fc97bcd3284baa34afed4d9f55f2c5c06424c49645526c92534a54b52946867606a89b738dcb9c481a4c5d
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,14 @@
|
|
1
|
+
## [0.9.0]
|
2
|
+
|
3
|
+
* Add a `--watch` option that will spawn fswatch | xargs and clear the screen
|
4
|
+
between runs (requires fswatch to gbe installed)
|
5
|
+
* Add "lib" as a default load path along with "test"
|
6
|
+
|
7
|
+
## [0.8.0]
|
8
|
+
|
9
|
+
* Add a `--yes-i-know` flag that will suppress the large warning when your test
|
10
|
+
suite runs over the 1.8s limit
|
11
|
+
|
1
12
|
## [0.7.0]
|
2
13
|
|
3
14
|
* Add a `tldt` alias for folks who have another executable named `tldr` on their
|
data/README.md
CHANGED
@@ -116,14 +116,17 @@ Usage: tldr [options] some_tests/**/*.rb some/path.rb:13 ...
|
|
116
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
|
-
-l, --load-path PATH Add one or more paths to the $LOAD_PATH (Default: ["test"])
|
119
|
+
-l, --load-path PATH Add one or more paths to the $LOAD_PATH (Default: ["lib", "test"])
|
120
120
|
-r, --reporter REPORTER Set a custom reporter class (Default: "TLDR::Reporters::Default")
|
121
121
|
--base-path PATH Change the working directory for all relative paths (Default: current working directory)
|
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
125
|
--[no-]warnings Print Ruby warnings (Default: true)
|
126
|
-
--
|
126
|
+
--watch Run your tests continuously on file save (requires 'fswatch' to be installed)
|
127
|
+
--yes-i-know Suppress TLDR report when suite runs over 1.8s
|
128
|
+
--i-am-being-watched [INTERNAL] Signals to tldr it is being invoked under --watch mode
|
129
|
+
--comment COMMENT [INTERNAL] No-op; used for multi-line execution instructions
|
127
130
|
```
|
128
131
|
|
129
132
|
After being parsed, all the CLI options are converted into a
|
@@ -160,6 +163,17 @@ with these caveats:
|
|
160
163
|
TLDR::Assertions::MinitestCompatibility` into the `TLDR` base class or
|
161
164
|
individual test classesJust set it
|
162
165
|
|
166
|
+
### Running tests continuously with --watch
|
167
|
+
|
168
|
+
The `tldr` CLI includes a `--watch` option which will watch for changes in any
|
169
|
+
of the configured load paths (`["test", "lib"]` by default) and then execute
|
170
|
+
your tests each time a file is changed. To keep the output up-to-date and easy
|
171
|
+
to scan, it will also clear your console before each run.
|
172
|
+
|
173
|
+
Here's what that might look like:
|
174
|
+
|
175
|
+

|
176
|
+
|
163
177
|
### Running TLDR with Rake
|
164
178
|
|
165
179
|
TLDR ships with a [very](lib/tldr/rake.rb) minimal rake task that simply shells
|
@@ -239,8 +253,9 @@ encountered multiple times, only the first hook will be registered. If the
|
|
239
253
|
|
240
254
|
#### Setting up the load path
|
241
255
|
|
242
|
-
|
243
|
-
|
256
|
+
By default, the `tldr` CLI adds `test` and `lib` directories to the load path
|
257
|
+
for you, but when running TLDR from a Ruby script, it doesn't set those up for
|
258
|
+
you.
|
244
259
|
|
245
260
|
If you want to require code in `test/` or `lib/` without using
|
246
261
|
`require_relative`, you'll need to add those directories to the load path. You
|
@@ -336,12 +351,26 @@ with the `--reporter` command line option. It can be set to any fully-qualified
|
|
336
351
|
class name that extends from
|
337
352
|
[TLDR::Reporters::Base](/lib/tldr/reporters/base.rb).
|
338
353
|
|
354
|
+
### I know my tests are over 1.8s, how do I suppress the huge output?
|
355
|
+
|
356
|
+
Plenty of test suites are over 1.8s and having TLDR repeatedly print out the
|
357
|
+
huge summary at the end of each test run can be distracting and make it harder
|
358
|
+
to spot test failures. If you know your test suite is too slow, you can simply
|
359
|
+
add the `--yes-i-know` flag
|
360
|
+
|
339
361
|
### What about mocking?
|
340
362
|
|
341
363
|
TLDR is laser-focused on running tests, so it doesn't provide a built-in mocking
|
342
364
|
facility. Might we interest you in a refreshing
|
343
365
|
[mocktail](https://github.com/testdouble/mocktail), instead?
|
344
366
|
|
367
|
+
## Contributing to TLDR
|
368
|
+
|
369
|
+
If you want to submit PRs on this repo, please know that the code style is
|
370
|
+
[Kirkland-style Ruby](https://mastodon.social/@searls/111137666157318482), where
|
371
|
+
method definitions have parentheses omitted but parentheses are generally
|
372
|
+
expected for method invocations.
|
373
|
+
|
345
374
|
## Acknowledgements
|
346
375
|
|
347
376
|
Thanks to [George Sheppard](https://github.com/fuzzmonkey) for freeing up the
|
data/lib/tldr/argv_parser.rb
CHANGED
@@ -4,7 +4,7 @@ class TLDR
|
|
4
4
|
class ArgvParser
|
5
5
|
PATTERN_FRIENDLY_SPLITTER = /,(?=(?:[^\/]*\/[^\/]*\/)*[^\/]*$)/
|
6
6
|
|
7
|
-
def parse
|
7
|
+
def parse args, options = {cli_defaults: true}
|
8
8
|
OptionParser.new do |opts|
|
9
9
|
opts.banner = "Usage: tldr [options] some_tests/**/*.rb some/path.rb:13 ..."
|
10
10
|
|
@@ -22,12 +22,12 @@ class TLDR
|
|
22
22
|
|
23
23
|
opts.on "-n", "#{CONFLAGS[:names]} PATTERN", "One or more names or /patterns/ of tests to run (like: foo_test, /test_foo.*/, Foo#foo_test)" do |name|
|
24
24
|
options[:names] ||= []
|
25
|
-
options[:names] += name.split
|
25
|
+
options[:names] += name.split(PATTERN_FRIENDLY_SPLITTER)
|
26
26
|
end
|
27
27
|
|
28
28
|
opts.on "#{CONFLAGS[:exclude_names]} PATTERN", "One or more names or /patterns/ NOT to run" do |exclude_name|
|
29
29
|
options[:exclude_names] ||= []
|
30
|
-
options[:exclude_names] += exclude_name.split
|
30
|
+
options[:exclude_names] += exclude_name.split(PATTERN_FRIENDLY_SPLITTER)
|
31
31
|
end
|
32
32
|
|
33
33
|
opts.on "#{CONFLAGS[:exclude_paths]} PATH", Array, "One or more paths NOT to run (like: foo.rb, \"test/bar/**\", baz.rb:3)" do |path|
|
@@ -53,7 +53,7 @@ class TLDR
|
|
53
53
|
options[:no_prepend] = true
|
54
54
|
end
|
55
55
|
|
56
|
-
opts.on "-l", "#{CONFLAGS[:load_paths]} PATH", Array, "Add one or more paths to the $LOAD_PATH (Default: [\"test\"])" do |load_path|
|
56
|
+
opts.on "-l", "#{CONFLAGS[:load_paths]} PATH", Array, "Add one or more paths to the $LOAD_PATH (Default: [\"lib\", \"test\"])" do |load_path|
|
57
57
|
options[:load_paths] ||= []
|
58
58
|
options[:load_paths] += load_path
|
59
59
|
end
|
@@ -82,7 +82,19 @@ class TLDR
|
|
82
82
|
options[:warnings] = warnings
|
83
83
|
end
|
84
84
|
|
85
|
-
opts.on
|
85
|
+
opts.on CONFLAGS[:watch], "Run your tests continuously on file save (requires 'fswatch' to be installed)" do
|
86
|
+
options[:watch] = true
|
87
|
+
end
|
88
|
+
|
89
|
+
opts.on CONFLAGS[:yes_i_know], "Suppress TLDR report when suite runs over 1.8s" do
|
90
|
+
options[:yes_i_know] = true
|
91
|
+
end
|
92
|
+
|
93
|
+
opts.on CONFLAGS[:i_am_being_watched], "[INTERNAL] Signals to tldr it is being invoked under --watch mode" do
|
94
|
+
options[:i_am_being_watched] = true
|
95
|
+
end
|
96
|
+
|
97
|
+
opts.on "--comment COMMENT", String, "[INTERNAL] No-op; used for multi-line execution instructions" do
|
86
98
|
# See "--comment" in lib/tldr/reporters/default.rb for an example of how this is used internally
|
87
99
|
end
|
88
100
|
end.parse!(args)
|
data/lib/tldr/assertions.rb
CHANGED
@@ -74,7 +74,7 @@ class TLDR
|
|
74
74
|
end
|
75
75
|
|
76
76
|
def assert_equal expected, actual, message = nil
|
77
|
-
message = Assertions.msg(message) { Assertions.diff
|
77
|
+
message = Assertions.msg(message) { Assertions.diff(expected, actual) }
|
78
78
|
assert expected == actual, message
|
79
79
|
end
|
80
80
|
|
@@ -158,7 +158,7 @@ class TLDR
|
|
158
158
|
"Expected #{Assertions.h(actual)} to match #{Assertions.h(matcher)}"
|
159
159
|
}
|
160
160
|
assert_respond_to matcher, :=~
|
161
|
-
matcher = Regexp.new
|
161
|
+
matcher = Regexp.new(Regexp.escape(matcher)) if String === matcher
|
162
162
|
assert matcher =~ actual, message
|
163
163
|
Regexp.last_match
|
164
164
|
end
|
@@ -294,7 +294,7 @@ class TLDR
|
|
294
294
|
"---Backtrace---",
|
295
295
|
TLDR.filter_backtrace(e.backtrace).join("\n"),
|
296
296
|
"---------------"
|
297
|
-
].compact.join
|
297
|
+
].compact.join("\n")
|
298
298
|
}
|
299
299
|
end
|
300
300
|
|
@@ -14,19 +14,19 @@ class TLDR
|
|
14
14
|
private
|
15
15
|
|
16
16
|
def trim_leading_frames backtrace
|
17
|
-
if (trimmed = backtrace.take_while { |frame| meaningful?
|
17
|
+
if (trimmed = backtrace.take_while { |frame| meaningful?(frame) }).any?
|
18
18
|
trimmed
|
19
19
|
end
|
20
20
|
end
|
21
21
|
|
22
22
|
def trim_internal_frames backtrace
|
23
|
-
if (trimmed = backtrace.select { |frame| meaningful?
|
23
|
+
if (trimmed = backtrace.select { |frame| meaningful?(frame) }).any?
|
24
24
|
trimmed
|
25
25
|
end
|
26
26
|
end
|
27
27
|
|
28
28
|
def meaningful? frame
|
29
|
-
!internal?
|
29
|
+
!internal?(frame)
|
30
30
|
end
|
31
31
|
|
32
32
|
def internal? frame
|
@@ -35,6 +35,6 @@ class TLDR
|
|
35
35
|
end
|
36
36
|
|
37
37
|
def self.filter_backtrace backtrace
|
38
|
-
BacktraceFilter.new.filter
|
38
|
+
BacktraceFilter.new.filter(backtrace)
|
39
39
|
end
|
40
40
|
end
|
data/lib/tldr/class_util.rb
CHANGED
@@ -2,13 +2,13 @@ class TLDR
|
|
2
2
|
module ClassUtil
|
3
3
|
def self.gather_descendants root_klass
|
4
4
|
root_klass.subclasses + root_klass.subclasses.flat_map { |subklass|
|
5
|
-
gather_descendants
|
5
|
+
gather_descendants(subklass)
|
6
6
|
}
|
7
7
|
end
|
8
8
|
|
9
9
|
def self.gather_tests klass
|
10
10
|
klass.instance_methods.grep(/^test_/).sort.map { |method|
|
11
|
-
Test.new
|
11
|
+
Test.new(klass, method)
|
12
12
|
}
|
13
13
|
end
|
14
14
|
end
|
data/lib/tldr/path_util.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
class TLDR
|
2
2
|
module PathUtil
|
3
3
|
def self.expand_paths path_strings, globs: true
|
4
|
-
path_strings = expand_globs
|
4
|
+
path_strings = expand_globs(path_strings) if globs
|
5
5
|
|
6
6
|
path_strings.flat_map { |path_string|
|
7
7
|
File.directory?(path_string) ? Dir["#{path_string}/**/*.rb"] : path_string
|
@@ -10,7 +10,7 @@ class TLDR
|
|
10
10
|
line_numbers = path_string.scan(/:(\d+)/).flatten.map(&:to_i)
|
11
11
|
|
12
12
|
if line_numbers.any?
|
13
|
-
line_numbers.map { |line_number| Location.new
|
13
|
+
line_numbers.map { |line_number| Location.new(absolute_path, line_number) }
|
14
14
|
else
|
15
15
|
[Location.new(absolute_path, nil)]
|
16
16
|
end
|
data/lib/tldr/planner.rb
CHANGED
@@ -8,14 +8,14 @@ class TLDR
|
|
8
8
|
|
9
9
|
def plan config
|
10
10
|
$VERBOSE = config.warnings
|
11
|
-
search_locations = PathUtil.expand_paths
|
11
|
+
search_locations = PathUtil.expand_paths(config.paths, globs: false)
|
12
12
|
|
13
|
-
prepend_load_paths
|
14
|
-
require_test_helper
|
15
|
-
require_tests
|
13
|
+
prepend_load_paths(config)
|
14
|
+
require_test_helper(config)
|
15
|
+
require_tests(search_locations)
|
16
16
|
|
17
17
|
tests = gather_tests
|
18
|
-
config.update_after_gathering_tests!
|
18
|
+
config.update_after_gathering_tests!(tests)
|
19
19
|
tests_to_run = prepend(
|
20
20
|
shuffle(
|
21
21
|
exclude_by_path(
|
@@ -40,7 +40,7 @@ class TLDR
|
|
40
40
|
config
|
41
41
|
)
|
42
42
|
|
43
|
-
Plan.new
|
43
|
+
Plan.new(tests_to_run, strategy)
|
44
44
|
end
|
45
45
|
|
46
46
|
private
|
@@ -53,9 +53,9 @@ class TLDR
|
|
53
53
|
|
54
54
|
def prepend tests, config
|
55
55
|
return tests if config.no_prepend
|
56
|
-
prepended_locations = PathUtil.expand_paths
|
56
|
+
prepended_locations = PathUtil.expand_paths(config.prepend_paths)
|
57
57
|
prepended, rest = tests.partition { |test|
|
58
|
-
PathUtil.locations_include_test?
|
58
|
+
PathUtil.locations_include_test?(prepended_locations, test)
|
59
59
|
}
|
60
60
|
prepended + rest
|
61
61
|
end
|
@@ -65,18 +65,18 @@ class TLDR
|
|
65
65
|
end
|
66
66
|
|
67
67
|
def exclude_by_path tests, exclude_paths
|
68
|
-
excluded_locations = PathUtil.expand_paths
|
68
|
+
excluded_locations = PathUtil.expand_paths(exclude_paths)
|
69
69
|
return tests if excluded_locations.empty?
|
70
70
|
|
71
71
|
tests.reject { |test|
|
72
|
-
PathUtil.locations_include_test?
|
72
|
+
PathUtil.locations_include_test?(excluded_locations, test)
|
73
73
|
}
|
74
74
|
end
|
75
75
|
|
76
76
|
def exclude_by_name tests, exclude_names
|
77
77
|
return tests if exclude_names.empty?
|
78
78
|
|
79
|
-
name_excludes = expand_names_with_patterns
|
79
|
+
name_excludes = expand_names_with_patterns(exclude_names)
|
80
80
|
|
81
81
|
tests.reject { |test|
|
82
82
|
name_excludes.any? { |filter|
|
@@ -90,14 +90,14 @@ class TLDR
|
|
90
90
|
return tests if line_specific_locations.empty?
|
91
91
|
|
92
92
|
tests.select { |test|
|
93
|
-
PathUtil.locations_include_test?
|
93
|
+
PathUtil.locations_include_test?(line_specific_locations, test)
|
94
94
|
}
|
95
95
|
end
|
96
96
|
|
97
97
|
def filter_by_name tests, names
|
98
98
|
return tests if names.empty?
|
99
99
|
|
100
|
-
name_filters = expand_names_with_patterns
|
100
|
+
name_filters = expand_names_with_patterns(names)
|
101
101
|
|
102
102
|
tests.select { |test|
|
103
103
|
name_filters.any? { |filter|
|
@@ -108,7 +108,7 @@ class TLDR
|
|
108
108
|
|
109
109
|
def prepend_load_paths config
|
110
110
|
config.load_paths.each do |load_path|
|
111
|
-
$LOAD_PATH.unshift
|
111
|
+
$LOAD_PATH.unshift(File.expand_path(load_path, Dir.pwd))
|
112
112
|
end
|
113
113
|
end
|
114
114
|
|
@@ -130,7 +130,7 @@ class TLDR
|
|
130
130
|
def expand_names_with_patterns names
|
131
131
|
names.map { |name|
|
132
132
|
if name.is_a?(String) && name =~ /^\/(.*)\/$/
|
133
|
-
Regexp.new
|
133
|
+
Regexp.new($1)
|
134
134
|
else
|
135
135
|
name
|
136
136
|
end
|
data/lib/tldr/rake.rb
CHANGED
data/lib/tldr/reporters/base.rb
CHANGED
@@ -1,13 +1,14 @@
|
|
1
1
|
class TLDR
|
2
2
|
module Reporters
|
3
3
|
class Default < Base
|
4
|
-
def initialize
|
4
|
+
def initialize config, out = $stdout, err = $stderr
|
5
5
|
super
|
6
6
|
@icons = @config.no_emoji ? IconProvider::Base.new : IconProvider::Emoji.new
|
7
7
|
end
|
8
8
|
|
9
9
|
def before_suite tests
|
10
|
-
|
10
|
+
clear_screen_if_being_watched!
|
11
|
+
@suite_start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC, :microsecond)
|
11
12
|
@out.print <<~MSG
|
12
13
|
Command: #{tldr_command} #{@config.to_full_args}
|
13
14
|
#{@icons.seed} #{CONFLAGS[:seed]} #{@config.seed}
|
@@ -31,32 +32,34 @@ class TLDR
|
|
31
32
|
end
|
32
33
|
end
|
33
34
|
|
34
|
-
def time_diff start, stop = Process.clock_gettime(Process::CLOCK_MONOTONIC, :microsecond)
|
35
|
-
((stop - start) / 1000.0).round
|
36
|
-
end
|
37
|
-
|
38
35
|
def after_tldr planned_tests, wip_tests, test_results
|
39
|
-
stop_time = Process.clock_gettime
|
36
|
+
stop_time = Process.clock_gettime(Process::CLOCK_MONOTONIC, :microsecond)
|
40
37
|
|
41
38
|
@out.print @icons.tldr
|
42
39
|
@err.print "\n\n"
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
40
|
+
|
41
|
+
if @config.yes_i_know
|
42
|
+
@err.print "🚨 TLDR! Display summary by omitting --yes-i-know"
|
43
|
+
else
|
44
|
+
wrap_in_horizontal_rule do
|
45
|
+
@err.print [
|
46
|
+
"too long; didn't run!",
|
47
|
+
"#{@icons.run} Completed #{test_results.size} of #{planned_tests.size} tests (#{((test_results.size.to_f / planned_tests.size) * 100).round}%) before running out of time.",
|
48
|
+
(<<~WIP.chomp if wip_tests.any?),
|
49
|
+
#{@icons.wip} #{plural(wip_tests.size, "test was", "tests were")} cancelled in progress:
|
50
|
+
#{wip_tests.map { |wip_test| " #{time_diff(wip_test.start_time, stop_time)}ms - #{describe(wip_test.test)}" }.join("\n")}
|
51
|
+
WIP
|
52
|
+
(<<~SLOW.chomp if test_results.any?),
|
53
|
+
#{@icons.slow} Your #{[10, test_results.size].min} slowest completed tests:
|
54
|
+
#{test_results.sort_by(&:runtime).last(10).reverse.map { |result| " #{result.runtime}ms - #{describe(result.test)}" }.join("\n")}
|
55
|
+
SLOW
|
56
|
+
describe_tests_that_didnt_finish(planned_tests, test_results),
|
57
|
+
"🙈 Suppress this summary with --yes-i-know"
|
58
|
+
].compact.join("\n\n")
|
59
|
+
end
|
57
60
|
end
|
58
61
|
|
59
|
-
after_suite
|
62
|
+
after_suite(test_results)
|
60
63
|
end
|
61
64
|
|
62
65
|
def after_fail_fast planned_tests, wip_tests, test_results, last_result
|
@@ -66,17 +69,17 @@ class TLDR
|
|
66
69
|
wrap_in_horizontal_rule do
|
67
70
|
@err.print [
|
68
71
|
"Failing fast after #{describe(last_result.test, last_result.relevant_location)} #{last_result.error? ? "errored" : "failed"}.",
|
69
|
-
("#{@icons.wip} #{plural
|
70
|
-
("#{@icons.not_run} #{plural
|
72
|
+
("#{@icons.wip} #{plural(wip_tests.size, "test was", "tests were")} cancelled in progress." if wip_tests.any?),
|
73
|
+
("#{@icons.not_run} #{plural(unrun_tests.size, "test was", "tests were")} not run at all." if unrun_tests.any?),
|
71
74
|
describe_tests_that_didnt_finish(planned_tests, test_results)
|
72
75
|
].compact.join("\n\n")
|
73
76
|
end
|
74
77
|
|
75
|
-
after_suite
|
78
|
+
after_suite(test_results)
|
76
79
|
end
|
77
80
|
|
78
81
|
def after_suite test_results
|
79
|
-
duration = time_diff
|
82
|
+
duration = time_diff(@suite_start_time)
|
80
83
|
test_results = test_results.sort_by { |result| [result.test.location.file, result.test.location.line] }
|
81
84
|
|
82
85
|
@err.print summarize_failures(test_results).join("\n\n")
|
@@ -102,11 +105,15 @@ class TLDR
|
|
102
105
|
|
103
106
|
private
|
104
107
|
|
108
|
+
def time_diff start, stop = Process.clock_gettime(Process::CLOCK_MONOTONIC, :microsecond)
|
109
|
+
((stop - start) / 1000.0).round
|
110
|
+
end
|
111
|
+
|
105
112
|
def summarize_failures results
|
106
113
|
failures = results.select { |result| result.failing? }
|
107
114
|
return failures if failures.empty?
|
108
115
|
|
109
|
-
["\n\nFailing tests:"] + failures.map.with_index { |result, i| summarize_result
|
116
|
+
["\n\nFailing tests:"] + failures.map.with_index { |result, i| summarize_result(result, i) }
|
110
117
|
end
|
111
118
|
|
112
119
|
def summarize_result result, index
|
@@ -138,22 +145,22 @@ class TLDR
|
|
138
145
|
rule = @icons.alarm + "=" * 20 + " ABORTED RUN " + "=" * 20 + @icons.alarm
|
139
146
|
@err.print "#{rule}\n\n"
|
140
147
|
yield
|
141
|
-
@err.print "\n\n#{rule}
|
148
|
+
@err.print "\n\n#{rule}"
|
142
149
|
end
|
143
150
|
|
144
151
|
def describe_tests_that_didnt_finish planned_tests, test_results
|
145
152
|
unrun = planned_tests - test_results.map(&:test)
|
146
153
|
return if unrun.empty?
|
147
154
|
|
148
|
-
unrun_locators = consolidate
|
155
|
+
unrun_locators = consolidate(unrun)
|
149
156
|
failed = test_results.select(&:failing?).map(&:test)
|
150
|
-
failed_locators = consolidate
|
157
|
+
failed_locators = consolidate(failed, exclude: unrun_locators)
|
151
158
|
suggested_locators = unrun_locators + [
|
152
|
-
("--comment \"Also include #{plural
|
153
|
-
] + failed_locators
|
159
|
+
("--comment \"Also include #{plural(failed.size, "test")} that failed:\"" if failed_locators.any?)
|
160
|
+
].compact + failed_locators
|
154
161
|
<<~MSG
|
155
|
-
#{@icons.rock_on} Run the #{plural
|
156
|
-
#{tldr_command} #{@config.to_full_args
|
162
|
+
#{@icons.rock_on} Run the #{plural(unrun.size, "test")} that didn't finish:
|
163
|
+
#{tldr_command} #{@config.to_full_args(exclude: [:paths])} #{suggested_locators.join(" \\\n ")}
|
157
164
|
MSG
|
158
165
|
end
|
159
166
|
|
@@ -166,6 +173,12 @@ class TLDR
|
|
166
173
|
def tldr_command
|
167
174
|
"#{"bundle exec " if defined?(Bundler)}tldr"
|
168
175
|
end
|
176
|
+
|
177
|
+
def clear_screen_if_being_watched!
|
178
|
+
if @config.i_am_being_watched
|
179
|
+
@out.print "\e[2J\e[f"
|
180
|
+
end
|
181
|
+
end
|
169
182
|
end
|
170
183
|
end
|
171
184
|
end
|
data/lib/tldr/runner.rb
CHANGED
@@ -6,7 +6,7 @@ class TLDR
|
|
6
6
|
@executor = Executor.new
|
7
7
|
@wip = Concurrent::Array.new
|
8
8
|
@results = Concurrent::Array.new
|
9
|
-
@run_aborted = Concurrent::AtomicBoolean.new
|
9
|
+
@run_aborted = Concurrent::AtomicBoolean.new(false)
|
10
10
|
end
|
11
11
|
|
12
12
|
def run config, plan
|
@@ -20,11 +20,11 @@ class TLDR
|
|
20
20
|
next if ENV["CI"] && !$stderr.tty?
|
21
21
|
next if @run_aborted.true?
|
22
22
|
@run_aborted.make_true
|
23
|
-
reporter.after_tldr
|
24
|
-
exit!
|
23
|
+
reporter.after_tldr(plan.tests, @wip.dup, @results.dup)
|
24
|
+
exit!(3)
|
25
25
|
end
|
26
26
|
|
27
|
-
sleep
|
27
|
+
sleep(1.8)
|
28
28
|
# Don't hard-kill the runner if user is debugging, it'll
|
29
29
|
# screw up their terminal slash be a bad time
|
30
30
|
if IRB.CurrentContext
|
@@ -41,8 +41,8 @@ class TLDR
|
|
41
41
|
end
|
42
42
|
|
43
43
|
unless @run_aborted.true?
|
44
|
-
reporter.after_suite
|
45
|
-
exit
|
44
|
+
reporter.after_suite(results)
|
45
|
+
exit(exit_code(results))
|
46
46
|
end
|
47
47
|
end
|
48
48
|
|
@@ -51,12 +51,12 @@ class TLDR
|
|
51
51
|
def run_test test, config, plan, reporter
|
52
52
|
e = nil
|
53
53
|
start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC, :microsecond)
|
54
|
-
wip_test = WIPTest.new
|
54
|
+
wip_test = WIPTest.new(test, start_time)
|
55
55
|
@wip << wip_test
|
56
56
|
runtime = time_it(start_time) do
|
57
57
|
instance = test.test_class.new
|
58
|
-
instance.setup if instance.respond_to?
|
59
|
-
if instance.respond_to?
|
58
|
+
instance.setup if instance.respond_to?(:setup)
|
59
|
+
if instance.respond_to?(:around)
|
60
60
|
did_run = false
|
61
61
|
instance.around {
|
62
62
|
did_run = true
|
@@ -66,15 +66,15 @@ class TLDR
|
|
66
66
|
else
|
67
67
|
instance.send(test.method_name)
|
68
68
|
end
|
69
|
-
instance.teardown if instance.respond_to?
|
69
|
+
instance.teardown if instance.respond_to?(:teardown)
|
70
70
|
rescue Skip, Failure, StandardError => e
|
71
71
|
end
|
72
72
|
TestResult.new(test, e, runtime).tap do |result|
|
73
73
|
next if @run_aborted.true?
|
74
74
|
@results << result
|
75
|
-
@wip.delete
|
76
|
-
reporter.after_test
|
77
|
-
fail_fast
|
75
|
+
@wip.delete(wip_test)
|
76
|
+
reporter.after_test(result)
|
77
|
+
fail_fast(reporter, plan, result) if result.failing? && config.fail_fast
|
78
78
|
end
|
79
79
|
end
|
80
80
|
|
@@ -82,8 +82,8 @@ class TLDR
|
|
82
82
|
unless @run_aborted.true?
|
83
83
|
@run_aborted.make_true
|
84
84
|
abort = proc do
|
85
|
-
reporter.after_fail_fast
|
86
|
-
exit!
|
85
|
+
reporter.after_fail_fast(plan.tests, @wip.dup, @results.dup, fast_failed_result)
|
86
|
+
exit!(exit_code([fast_failed_result]))
|
87
87
|
end
|
88
88
|
|
89
89
|
if IRB.CurrentContext
|
@@ -94,7 +94,7 @@ class TLDR
|
|
94
94
|
end
|
95
95
|
end
|
96
96
|
|
97
|
-
def time_it
|
97
|
+
def time_it start
|
98
98
|
yield
|
99
99
|
((Process.clock_gettime(Process::CLOCK_MONOTONIC, :microsecond) - start) / 1000.0).round
|
100
100
|
end
|
data/lib/tldr/strategizer.rb
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
class TLDR
|
2
2
|
class Strategizer
|
3
|
-
Strategy = Struct.new
|
3
|
+
Strategy = Struct.new(:parallel?, :prepend_sequential_tests,
|
4
4
|
:parallel_tests_and_groups, :append_sequential_tests,
|
5
|
-
keyword_init: true
|
5
|
+
keyword_init: true)
|
6
6
|
|
7
7
|
# Combine all discovered test methods with any methods grouped by run_these_together!
|
8
8
|
#
|
@@ -16,11 +16,11 @@ class TLDR
|
|
16
16
|
thread_unsafe_tests, thread_safe_tests = partition_unsafe(all_tests, thread_unsafe_test_groups)
|
17
17
|
prepend_sequential_tests, append_sequential_tests = partition_prepend(thread_unsafe_tests, config)
|
18
18
|
|
19
|
-
grouped_tests = prepare_run_together_groups
|
19
|
+
grouped_tests = prepare_run_together_groups(run_these_together_groups, thread_safe_tests, append_sequential_tests)
|
20
20
|
already_included_groups = []
|
21
21
|
parallel_tests_and_groups = thread_safe_tests.map { |test|
|
22
|
-
if (group = grouped_tests.find { |group| group.tests.include?
|
23
|
-
if already_included_groups.include?
|
22
|
+
if (group = grouped_tests.find { |group| group.tests.include?(test) })
|
23
|
+
if already_included_groups.include?(group)
|
24
24
|
next
|
25
25
|
elsif (other = already_included_groups.find { |other| (group.tests & other.tests).any? })
|
26
26
|
other.tests |= group.tests
|
@@ -49,7 +49,7 @@ class TLDR
|
|
49
49
|
|
50
50
|
def partition_unsafe tests, thread_unsafe_test_groups
|
51
51
|
tests.partition { |test|
|
52
|
-
thread_unsafe_test_groups.any? { |group| group.tests.include?
|
52
|
+
thread_unsafe_test_groups.any? { |group| group.tests.include?(test) }
|
53
53
|
}
|
54
54
|
end
|
55
55
|
|
@@ -57,10 +57,10 @@ class TLDR
|
|
57
57
|
# Suboptimal, but we do indeed need to do this work in two places ¯\_(ツ)_/¯
|
58
58
|
def partition_prepend thread_unsafe_tests, config
|
59
59
|
prepend_paths = config.no_prepend ? [] : config.prepend_paths
|
60
|
-
locations = PathUtil.expand_paths
|
60
|
+
locations = PathUtil.expand_paths(prepend_paths)
|
61
61
|
|
62
62
|
thread_unsafe_tests.partition { |test|
|
63
|
-
PathUtil.locations_include_test?
|
63
|
+
PathUtil.locations_include_test?(locations, test)
|
64
64
|
}
|
65
65
|
end
|
66
66
|
|
data/lib/tldr/value/config.rb
CHANGED
@@ -17,6 +17,9 @@ class TLDR
|
|
17
17
|
base_path: "--base-path",
|
18
18
|
no_dotfile: "--no-dotfile",
|
19
19
|
warnings: "--[no-]warnings",
|
20
|
+
watch: "--watch",
|
21
|
+
yes_i_know: "--yes-i-know",
|
22
|
+
i_am_being_watched: "--i-am-being-watched",
|
20
23
|
paths: nil
|
21
24
|
}.freeze
|
22
25
|
|
@@ -26,7 +29,7 @@ class TLDR
|
|
26
29
|
:paths, :seed, :no_helper, :verbose, :reporter,
|
27
30
|
:helper_paths, :load_paths, :parallel, :names, :fail_fast, :no_emoji,
|
28
31
|
:prepend_paths, :no_prepend, :exclude_paths, :exclude_names, :base_path,
|
29
|
-
:no_dotfile, :warnings,
|
32
|
+
:no_dotfile, :warnings, :watch, :yes_i_know, :i_am_being_watched,
|
30
33
|
# Internal properties
|
31
34
|
:config_intended_for_merge_only, :seed_set_intentionally, :cli_defaults
|
32
35
|
].freeze
|
@@ -64,14 +67,17 @@ class TLDR
|
|
64
67
|
exclude_paths: [],
|
65
68
|
exclude_names: [],
|
66
69
|
base_path: nil,
|
67
|
-
warnings: true
|
70
|
+
warnings: true,
|
71
|
+
watch: false,
|
72
|
+
yes_i_know: false,
|
73
|
+
i_am_being_watched: false
|
68
74
|
}
|
69
75
|
|
70
76
|
if cli_defaults
|
71
77
|
common.merge(
|
72
78
|
paths: Dir["test/**/*_test.rb", "test/**/test_*.rb"],
|
73
79
|
helper_paths: ["test/helper.rb"],
|
74
|
-
load_paths: ["test"],
|
80
|
+
load_paths: ["lib", "test"],
|
75
81
|
prepend_paths: [MOST_RECENTLY_MODIFIED_TAG]
|
76
82
|
)
|
77
83
|
else
|
@@ -101,7 +107,7 @@ class TLDR
|
|
101
107
|
end
|
102
108
|
|
103
109
|
# Booleans
|
104
|
-
[:no_helper, :verbose, :fail_fast, :no_emoji, :no_prepend, :warnings].each do |key|
|
110
|
+
[:no_helper, :verbose, :fail_fast, :no_emoji, :no_prepend, :warnings, :yes_i_know, :i_am_being_watched].each do |key|
|
105
111
|
merged_args[key] = defaults[key] if merged_args[key].nil?
|
106
112
|
end
|
107
113
|
|
@@ -129,26 +135,34 @@ class TLDR
|
|
129
135
|
|
130
136
|
self.prepend_paths = prepend_paths.map { |path|
|
131
137
|
if path == MOST_RECENTLY_MODIFIED_TAG
|
132
|
-
most_recently_modified_test_file
|
138
|
+
most_recently_modified_test_file(tests)
|
133
139
|
else
|
134
140
|
path
|
135
141
|
end
|
136
142
|
}.compact
|
137
143
|
end
|
138
144
|
|
139
|
-
def to_full_args
|
140
|
-
to_cli_argv(
|
145
|
+
def to_full_args exclude: [], ensure_args: []
|
146
|
+
argv = to_cli_argv(
|
141
147
|
CONFLAGS.keys -
|
142
148
|
exclude - [
|
143
|
-
(:seed unless seed_set_intentionally)
|
149
|
+
(:seed unless seed_set_intentionally),
|
150
|
+
:watch,
|
151
|
+
:i_am_being_watched
|
144
152
|
]
|
145
|
-
)
|
153
|
+
)
|
154
|
+
|
155
|
+
ensure_args.each do |arg|
|
156
|
+
argv << arg unless argv.include?(arg)
|
157
|
+
end
|
158
|
+
|
159
|
+
argv.join(" ")
|
146
160
|
end
|
147
161
|
|
148
|
-
def to_single_path_args
|
162
|
+
def to_single_path_args path
|
149
163
|
argv = to_cli_argv(CONFLAGS.keys - [
|
150
164
|
:seed, :parallel, :names, :fail_fast, :paths, :prepend_paths,
|
151
|
-
:no_prepend, :exclude_paths
|
165
|
+
:no_prepend, :exclude_paths, :watch, :i_am_being_watched
|
152
166
|
])
|
153
167
|
|
154
168
|
(argv + [stringify(:paths, path)]).join(" ")
|
@@ -210,7 +224,7 @@ class TLDR
|
|
210
224
|
end
|
211
225
|
end
|
212
226
|
|
213
|
-
def most_recently_modified_test_file
|
227
|
+
def most_recently_modified_test_file tests
|
214
228
|
return if tests.empty?
|
215
229
|
|
216
230
|
tests.max_by { |test| File.mtime(test.file) }.file
|
@@ -220,11 +234,11 @@ class TLDR
|
|
220
234
|
# ASAP, even before globbing to find default paths of tests. If there is
|
221
235
|
# a way to change all of our Dir.glob calls to be relative to base_path
|
222
236
|
# without a loss in accuracy, would love to not have to use Dir.chdir!
|
223
|
-
def change_working_directory_because_i_am_bad_and_i_should_feel_bad!
|
237
|
+
def change_working_directory_because_i_am_bad_and_i_should_feel_bad! base_path
|
224
238
|
Dir.chdir(base_path) unless base_path.nil?
|
225
239
|
end
|
226
240
|
|
227
|
-
def merge_dotfile_args
|
241
|
+
def merge_dotfile_args args
|
228
242
|
return args if args[:no_dotfile] || !File.exist?(".tldr.yml")
|
229
243
|
require "yaml"
|
230
244
|
|
data/lib/tldr/value/location.rb
CHANGED
data/lib/tldr/value/plan.rb
CHANGED
data/lib/tldr/value/test.rb
CHANGED
@@ -1,17 +1,17 @@
|
|
1
1
|
class TLDR
|
2
|
-
Test = Struct.new
|
2
|
+
Test = Struct.new(:test_class, :method_name) do
|
3
3
|
attr_reader :file, :line, :location
|
4
4
|
|
5
5
|
def initialize(*args)
|
6
6
|
super
|
7
7
|
@file, @line = SorbetCompatibility.unwrap_method(test_class.instance_method(method_name)).source_location
|
8
|
-
@location = Location.new
|
8
|
+
@location = Location.new(file, line)
|
9
9
|
end
|
10
10
|
|
11
11
|
# Memoizing at call time, because re-parsing isn't free and isn't usually necessary
|
12
12
|
def end_line
|
13
13
|
@end_line ||= begin
|
14
|
-
test_method = SorbetCompatibility.unwrap_method
|
14
|
+
test_method = SorbetCompatibility.unwrap_method(test_class.instance_method(method_name))
|
15
15
|
RubyVM::AbstractSyntaxTree.of(test_method).last_lineno
|
16
16
|
end
|
17
17
|
end
|
@@ -1,16 +1,16 @@
|
|
1
1
|
class TLDR
|
2
|
-
TestGroup = Struct.new
|
2
|
+
TestGroup = Struct.new(:configuration) do
|
3
3
|
attr_writer :tests
|
4
4
|
|
5
5
|
def tests
|
6
6
|
@tests ||= configuration.flat_map { |(klass, method)|
|
7
|
-
klass = Kernel.const_get(klass) if klass.is_a?
|
7
|
+
klass = Kernel.const_get(klass) if klass.is_a?(String)
|
8
8
|
if method.nil?
|
9
9
|
([klass] + ClassUtil.gather_descendants(klass)).flat_map { |klass|
|
10
10
|
ClassUtil.gather_tests(klass)
|
11
11
|
}
|
12
12
|
else
|
13
|
-
Test.new
|
13
|
+
Test.new(klass, method)
|
14
14
|
end
|
15
15
|
}
|
16
16
|
end
|
data/lib/tldr/value/wip_test.rb
CHANGED
data/lib/tldr/version.rb
CHANGED
data/lib/tldr/watcher.rb
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
class TLDR
|
2
|
+
class Watcher
|
3
|
+
def watch config
|
4
|
+
require_fs_watch!
|
5
|
+
command = "fswatch -o #{config.load_paths.reverse.join(" ")} | xargs -n1 -I{} #{tldr_command} #{config.to_full_args}"
|
6
|
+
|
7
|
+
puts <<~MSG
|
8
|
+
Watching #{config.load_paths.map(&:inspect).join(", ")} for changes...
|
9
|
+
MSG
|
10
|
+
|
11
|
+
exec command
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def require_fs_watch!
|
17
|
+
`which fswatch`
|
18
|
+
return if $?.success?
|
19
|
+
|
20
|
+
warn <<~MSG
|
21
|
+
Error: fswatch must be installed and on your PATH to run TLDR in --watch mode
|
22
|
+
|
23
|
+
See: https://github.com/emcrisostomo/fswatch
|
24
|
+
MSG
|
25
|
+
exit 1
|
26
|
+
end
|
27
|
+
|
28
|
+
def tldr_command
|
29
|
+
"#{"bundle exec " if defined?(Bundler)}tldr"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
data/lib/tldr.rb
CHANGED
@@ -16,6 +16,7 @@ require_relative "tldr/sorbet_compatibility"
|
|
16
16
|
require_relative "tldr/strategizer"
|
17
17
|
require_relative "tldr/value"
|
18
18
|
require_relative "tldr/version"
|
19
|
+
require_relative "tldr/watcher"
|
19
20
|
|
20
21
|
class TLDR
|
21
22
|
include Assertions
|
@@ -29,25 +30,29 @@ class TLDR
|
|
29
30
|
|
30
31
|
module Run
|
31
32
|
def self.cli argv
|
32
|
-
config = ArgvParser.new.parse
|
33
|
-
tests
|
33
|
+
config = ArgvParser.new.parse(argv)
|
34
|
+
tests(config)
|
34
35
|
end
|
35
36
|
|
36
37
|
def self.tests config = Config.new
|
37
|
-
|
38
|
+
if config.watch
|
39
|
+
Watcher.new.watch(config)
|
40
|
+
else
|
41
|
+
Runner.new.run(config, Planner.new.plan(config))
|
42
|
+
end
|
38
43
|
end
|
39
44
|
|
40
45
|
@@at_exit_registered = false
|
41
46
|
def self.at_exit! config = Config.new
|
42
47
|
# Ignore at_exit when running tldr CLI, since that will run any tests
|
43
|
-
return if $PROGRAM_NAME.end_with?
|
48
|
+
return if $PROGRAM_NAME.end_with?("tldr")
|
44
49
|
# Also ignore if we're running from within our rake task
|
45
|
-
return if caller.any? { |line| line.include?
|
50
|
+
return if caller.any? { |line| line.include?("lib/tldr/rake.rb") }
|
46
51
|
# Ignore at_exit when we've already registered an at_exit hook
|
47
52
|
return if @@at_exit_registered
|
48
53
|
|
49
54
|
Kernel.at_exit do
|
50
|
-
Run.tests
|
55
|
+
Run.tests(config)
|
51
56
|
end
|
52
57
|
|
53
58
|
@@at_exit_registered = true
|
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.9.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-10-
|
12
|
+
date: 2023-10-04 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: super_diff
|
@@ -85,6 +85,7 @@ files:
|
|
85
85
|
- lib/tldr/value/test_result.rb
|
86
86
|
- lib/tldr/value/wip_test.rb
|
87
87
|
- lib/tldr/version.rb
|
88
|
+
- lib/tldr/watcher.rb
|
88
89
|
- script/setup
|
89
90
|
- script/test
|
90
91
|
homepage: https://github.com/tenderlove/tldr
|