tldr 0.8.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 +6 -0
- data/README.md +19 -5
- data/lib/tldr/argv_parser.rb +13 -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 +22 -15
- data/lib/tldr/runner.rb +16 -16
- data/lib/tldr/strategizer.rb +8 -8
- data/lib/tldr/value/config.rb +26 -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 +2 -1
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,9 @@
|
|
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
|
+
|
1
7
|
## [0.8.0]
|
2
8
|
|
3
9
|
* Add a `--yes-i-know` flag that will suppress the large warning when your test
|
data/README.md
CHANGED
@@ -116,15 +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
|
+
--watch Run your tests continuously on file save (requires 'fswatch' to be installed)
|
126
127
|
--yes-i-know Suppress TLDR report when suite runs over 1.8s
|
127
|
-
--
|
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
|
128
130
|
```
|
129
131
|
|
130
132
|
After being parsed, all the CLI options are converted into a
|
@@ -161,6 +163,17 @@ with these caveats:
|
|
161
163
|
TLDR::Assertions::MinitestCompatibility` into the `TLDR` base class or
|
162
164
|
individual test classesJust set it
|
163
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
|
+
|
164
177
|
### Running TLDR with Rake
|
165
178
|
|
166
179
|
TLDR ships with a [very](lib/tldr/rake.rb) minimal rake task that simply shells
|
@@ -240,8 +253,9 @@ encountered multiple times, only the first hook will be registered. If the
|
|
240
253
|
|
241
254
|
#### Setting up the load path
|
242
255
|
|
243
|
-
|
244
|
-
|
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.
|
245
259
|
|
246
260
|
If you want to require code in `test/` or `lib/` without using
|
247
261
|
`require_relative`, you'll need to add those directories to the load path. You
|
@@ -350,7 +364,7 @@ TLDR is laser-focused on running tests, so it doesn't provide a built-in mocking
|
|
350
364
|
facility. Might we interest you in a refreshing
|
351
365
|
[mocktail](https://github.com/testdouble/mocktail), instead?
|
352
366
|
|
353
|
-
|
367
|
+
## Contributing to TLDR
|
354
368
|
|
355
369
|
If you want to submit PRs on this repo, please know that the code style is
|
356
370
|
[Kirkland-style Ruby](https://mastodon.social/@searls/111137666157318482), where
|
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,11 +82,19 @@ class TLDR
|
|
82
82
|
options[:warnings] = warnings
|
83
83
|
end
|
84
84
|
|
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
|
+
|
85
89
|
opts.on CONFLAGS[:yes_i_know], "Suppress TLDR report when suite runs over 1.8s" do
|
86
90
|
options[:yes_i_know] = true
|
87
91
|
end
|
88
92
|
|
89
|
-
opts.on
|
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
|
90
98
|
# See "--comment" in lib/tldr/reporters/default.rb for an example of how this is used internally
|
91
99
|
end
|
92
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}
|
@@ -32,7 +33,7 @@ class TLDR
|
|
32
33
|
end
|
33
34
|
|
34
35
|
def after_tldr planned_tests, wip_tests, test_results
|
35
|
-
stop_time = Process.clock_gettime
|
36
|
+
stop_time = Process.clock_gettime(Process::CLOCK_MONOTONIC, :microsecond)
|
36
37
|
|
37
38
|
@out.print @icons.tldr
|
38
39
|
@err.print "\n\n"
|
@@ -45,7 +46,7 @@ class TLDR
|
|
45
46
|
"too long; didn't run!",
|
46
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.",
|
47
48
|
(<<~WIP.chomp if wip_tests.any?),
|
48
|
-
#{@icons.wip} #{plural
|
49
|
+
#{@icons.wip} #{plural(wip_tests.size, "test was", "tests were")} cancelled in progress:
|
49
50
|
#{wip_tests.map { |wip_test| " #{time_diff(wip_test.start_time, stop_time)}ms - #{describe(wip_test.test)}" }.join("\n")}
|
50
51
|
WIP
|
51
52
|
(<<~SLOW.chomp if test_results.any?),
|
@@ -58,7 +59,7 @@ class TLDR
|
|
58
59
|
end
|
59
60
|
end
|
60
61
|
|
61
|
-
after_suite
|
62
|
+
after_suite(test_results)
|
62
63
|
end
|
63
64
|
|
64
65
|
def after_fail_fast planned_tests, wip_tests, test_results, last_result
|
@@ -68,17 +69,17 @@ class TLDR
|
|
68
69
|
wrap_in_horizontal_rule do
|
69
70
|
@err.print [
|
70
71
|
"Failing fast after #{describe(last_result.test, last_result.relevant_location)} #{last_result.error? ? "errored" : "failed"}.",
|
71
|
-
("#{@icons.wip} #{plural
|
72
|
-
("#{@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?),
|
73
74
|
describe_tests_that_didnt_finish(planned_tests, test_results)
|
74
75
|
].compact.join("\n\n")
|
75
76
|
end
|
76
77
|
|
77
|
-
after_suite
|
78
|
+
after_suite(test_results)
|
78
79
|
end
|
79
80
|
|
80
81
|
def after_suite test_results
|
81
|
-
duration = time_diff
|
82
|
+
duration = time_diff(@suite_start_time)
|
82
83
|
test_results = test_results.sort_by { |result| [result.test.location.file, result.test.location.line] }
|
83
84
|
|
84
85
|
@err.print summarize_failures(test_results).join("\n\n")
|
@@ -112,7 +113,7 @@ class TLDR
|
|
112
113
|
failures = results.select { |result| result.failing? }
|
113
114
|
return failures if failures.empty?
|
114
115
|
|
115
|
-
["\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) }
|
116
117
|
end
|
117
118
|
|
118
119
|
def summarize_result result, index
|
@@ -151,15 +152,15 @@ class TLDR
|
|
151
152
|
unrun = planned_tests - test_results.map(&:test)
|
152
153
|
return if unrun.empty?
|
153
154
|
|
154
|
-
unrun_locators = consolidate
|
155
|
+
unrun_locators = consolidate(unrun)
|
155
156
|
failed = test_results.select(&:failing?).map(&:test)
|
156
|
-
failed_locators = consolidate
|
157
|
+
failed_locators = consolidate(failed, exclude: unrun_locators)
|
157
158
|
suggested_locators = unrun_locators + [
|
158
|
-
("--comment \"Also include #{plural
|
159
|
+
("--comment \"Also include #{plural(failed.size, "test")} that failed:\"" if failed_locators.any?)
|
159
160
|
].compact + failed_locators
|
160
161
|
<<~MSG
|
161
|
-
#{@icons.rock_on} Run the #{plural
|
162
|
-
#{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 ")}
|
163
164
|
MSG
|
164
165
|
end
|
165
166
|
|
@@ -172,6 +173,12 @@ class TLDR
|
|
172
173
|
def tldr_command
|
173
174
|
"#{"bundle exec " if defined?(Bundler)}tldr"
|
174
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
|
175
182
|
end
|
176
183
|
end
|
177
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,7 +17,9 @@ class TLDR
|
|
17
17
|
base_path: "--base-path",
|
18
18
|
no_dotfile: "--no-dotfile",
|
19
19
|
warnings: "--[no-]warnings",
|
20
|
+
watch: "--watch",
|
20
21
|
yes_i_know: "--yes-i-know",
|
22
|
+
i_am_being_watched: "--i-am-being-watched",
|
21
23
|
paths: nil
|
22
24
|
}.freeze
|
23
25
|
|
@@ -27,7 +29,7 @@ class TLDR
|
|
27
29
|
:paths, :seed, :no_helper, :verbose, :reporter,
|
28
30
|
:helper_paths, :load_paths, :parallel, :names, :fail_fast, :no_emoji,
|
29
31
|
:prepend_paths, :no_prepend, :exclude_paths, :exclude_names, :base_path,
|
30
|
-
:no_dotfile, :warnings, :yes_i_know,
|
32
|
+
:no_dotfile, :warnings, :watch, :yes_i_know, :i_am_being_watched,
|
31
33
|
# Internal properties
|
32
34
|
:config_intended_for_merge_only, :seed_set_intentionally, :cli_defaults
|
33
35
|
].freeze
|
@@ -66,14 +68,16 @@ class TLDR
|
|
66
68
|
exclude_names: [],
|
67
69
|
base_path: nil,
|
68
70
|
warnings: true,
|
69
|
-
|
71
|
+
watch: false,
|
72
|
+
yes_i_know: false,
|
73
|
+
i_am_being_watched: false
|
70
74
|
}
|
71
75
|
|
72
76
|
if cli_defaults
|
73
77
|
common.merge(
|
74
78
|
paths: Dir["test/**/*_test.rb", "test/**/test_*.rb"],
|
75
79
|
helper_paths: ["test/helper.rb"],
|
76
|
-
load_paths: ["test"],
|
80
|
+
load_paths: ["lib", "test"],
|
77
81
|
prepend_paths: [MOST_RECENTLY_MODIFIED_TAG]
|
78
82
|
)
|
79
83
|
else
|
@@ -103,7 +107,7 @@ class TLDR
|
|
103
107
|
end
|
104
108
|
|
105
109
|
# Booleans
|
106
|
-
[:no_helper, :verbose, :fail_fast, :no_emoji, :no_prepend, :warnings, :yes_i_know].each do |key|
|
110
|
+
[:no_helper, :verbose, :fail_fast, :no_emoji, :no_prepend, :warnings, :yes_i_know, :i_am_being_watched].each do |key|
|
107
111
|
merged_args[key] = defaults[key] if merged_args[key].nil?
|
108
112
|
end
|
109
113
|
|
@@ -131,26 +135,34 @@ class TLDR
|
|
131
135
|
|
132
136
|
self.prepend_paths = prepend_paths.map { |path|
|
133
137
|
if path == MOST_RECENTLY_MODIFIED_TAG
|
134
|
-
most_recently_modified_test_file
|
138
|
+
most_recently_modified_test_file(tests)
|
135
139
|
else
|
136
140
|
path
|
137
141
|
end
|
138
142
|
}.compact
|
139
143
|
end
|
140
144
|
|
141
|
-
def to_full_args
|
142
|
-
to_cli_argv(
|
145
|
+
def to_full_args exclude: [], ensure_args: []
|
146
|
+
argv = to_cli_argv(
|
143
147
|
CONFLAGS.keys -
|
144
148
|
exclude - [
|
145
|
-
(:seed unless seed_set_intentionally)
|
149
|
+
(:seed unless seed_set_intentionally),
|
150
|
+
:watch,
|
151
|
+
:i_am_being_watched
|
146
152
|
]
|
147
|
-
)
|
153
|
+
)
|
154
|
+
|
155
|
+
ensure_args.each do |arg|
|
156
|
+
argv << arg unless argv.include?(arg)
|
157
|
+
end
|
158
|
+
|
159
|
+
argv.join(" ")
|
148
160
|
end
|
149
161
|
|
150
|
-
def to_single_path_args
|
162
|
+
def to_single_path_args path
|
151
163
|
argv = to_cli_argv(CONFLAGS.keys - [
|
152
164
|
:seed, :parallel, :names, :fail_fast, :paths, :prepend_paths,
|
153
|
-
:no_prepend, :exclude_paths
|
165
|
+
:no_prepend, :exclude_paths, :watch, :i_am_being_watched
|
154
166
|
])
|
155
167
|
|
156
168
|
(argv + [stringify(:paths, path)]).join(" ")
|
@@ -212,7 +224,7 @@ class TLDR
|
|
212
224
|
end
|
213
225
|
end
|
214
226
|
|
215
|
-
def most_recently_modified_test_file
|
227
|
+
def most_recently_modified_test_file tests
|
216
228
|
return if tests.empty?
|
217
229
|
|
218
230
|
tests.max_by { |test| File.mtime(test.file) }.file
|
@@ -222,11 +234,11 @@ class TLDR
|
|
222
234
|
# ASAP, even before globbing to find default paths of tests. If there is
|
223
235
|
# a way to change all of our Dir.glob calls to be relative to base_path
|
224
236
|
# without a loss in accuracy, would love to not have to use Dir.chdir!
|
225
|
-
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
|
226
238
|
Dir.chdir(base_path) unless base_path.nil?
|
227
239
|
end
|
228
240
|
|
229
|
-
def merge_dotfile_args
|
241
|
+
def merge_dotfile_args args
|
230
242
|
return args if args[:no_dotfile] || !File.exist?(".tldr.yml")
|
231
243
|
require "yaml"
|
232
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
|
@@ -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
|