tldr 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.standard.yml +3 -0
- data/CHANGELOG.md +5 -0
- data/LICENSE.txt +21 -0
- data/README.md +213 -0
- data/Rakefile +14 -0
- data/exe/tldr +5 -0
- data/lib/tldr/argv_parser.rb +94 -0
- data/lib/tldr/assertions/minitest_compatibility.rb +38 -0
- data/lib/tldr/assertions.rb +365 -0
- data/lib/tldr/backtrace_filter.rb +44 -0
- data/lib/tldr/error.rb +7 -0
- data/lib/tldr/planner.rb +170 -0
- data/lib/tldr/reporters/base.rb +36 -0
- data/lib/tldr/reporters/default.rb +167 -0
- data/lib/tldr/reporters/icon_provider.rb +93 -0
- data/lib/tldr/reporters.rb +4 -0
- data/lib/tldr/runner.rb +113 -0
- data/lib/tldr/skippable.rb +7 -0
- data/lib/tldr/sorbet_compatibility.rb +9 -0
- data/lib/tldr/value/config.rb +217 -0
- data/lib/tldr/value/location.rb +15 -0
- data/lib/tldr/value/plan.rb +3 -0
- data/lib/tldr/value/test.rb +23 -0
- data/lib/tldr/value/test_result.rb +62 -0
- data/lib/tldr/value/wip_test.rb +3 -0
- data/lib/tldr/value.rb +6 -0
- data/lib/tldr/version.rb +3 -0
- data/lib/tldr.rb +41 -0
- data/script/parse +6 -0
- data/script/run +6 -0
- data/script/test +25 -0
- metadata +108 -0
@@ -0,0 +1,365 @@
|
|
1
|
+
# While all the methods in this file were written for TLDR, they were designed
|
2
|
+
# to maximize compatibility with minitest's assertions API and messages here:
|
3
|
+
#
|
4
|
+
# https://github.com/minitest/minitest/blob/master/lib/minitest/assertions.rb
|
5
|
+
#
|
6
|
+
# As a result, many implementations are extremely similar to those found in
|
7
|
+
# minitest. Any such implementations are Copyright © Ryan Davis, seattle.rb and
|
8
|
+
# distributed under the MIT License
|
9
|
+
|
10
|
+
require "pp"
|
11
|
+
require "super_diff"
|
12
|
+
require_relative "assertions/minitest_compatibility"
|
13
|
+
|
14
|
+
class TLDR
|
15
|
+
module Assertions
|
16
|
+
def self.h obj
|
17
|
+
obj.pretty_inspect.chomp
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.msg message = nil, &default
|
21
|
+
proc {
|
22
|
+
message = message.call if Proc === message
|
23
|
+
[message.to_s, default.call].reject(&:empty?).join("\n")
|
24
|
+
}
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.diff expected, actual
|
28
|
+
SuperDiff::EqualityMatchers::Main.call(expected:, actual:)
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.capture_io
|
32
|
+
captured_stdout, captured_stderr = StringIO.new, StringIO.new
|
33
|
+
|
34
|
+
original_stdout, original_stderr = $stdout, $stderr
|
35
|
+
$stdout, $stderr = captured_stdout, captured_stderr
|
36
|
+
|
37
|
+
yield
|
38
|
+
|
39
|
+
[captured_stdout.string, captured_stderr.string]
|
40
|
+
ensure
|
41
|
+
$stdout = original_stdout
|
42
|
+
$stderr = original_stderr
|
43
|
+
end
|
44
|
+
|
45
|
+
def assert bool, message = nil
|
46
|
+
message ||= "Expected #{Assertions.h(bool)} to be truthy"
|
47
|
+
|
48
|
+
if bool
|
49
|
+
true
|
50
|
+
else
|
51
|
+
message = message.call if Proc === message
|
52
|
+
fail Failure, message
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def refute test, message = nil
|
57
|
+
message ||= Assertions.msg(message) { "Expected #{Assertions.h(test)} to not be truthy" }
|
58
|
+
assert !test, message
|
59
|
+
end
|
60
|
+
|
61
|
+
def assert_empty obj, message = nil
|
62
|
+
message = Assertions.msg(message) {
|
63
|
+
"Expected #{Assertions.h(obj)} to be empty"
|
64
|
+
}
|
65
|
+
|
66
|
+
assert_respond_to obj, :empty?
|
67
|
+
assert obj.empty?, message
|
68
|
+
end
|
69
|
+
|
70
|
+
def refute_empty obj, message = nil
|
71
|
+
message = Assertions.msg(message) { "Expected #{Assertions.h(obj)} to not be empty" }
|
72
|
+
assert_respond_to obj, :empty?
|
73
|
+
refute obj.empty?, message
|
74
|
+
end
|
75
|
+
|
76
|
+
def assert_equal expected, actual, message = nil
|
77
|
+
message = Assertions.msg(message) { Assertions.diff expected, actual }
|
78
|
+
assert expected == actual, message
|
79
|
+
end
|
80
|
+
|
81
|
+
def refute_equal expected, actual, message = nil
|
82
|
+
message = Assertions.msg(message) {
|
83
|
+
"Expected #{Assertions.h(actual)} to not be equal to #{Assertions.h(expected)}"
|
84
|
+
}
|
85
|
+
refute expected == actual, message
|
86
|
+
end
|
87
|
+
|
88
|
+
def assert_in_delta expected, actual, delta, message = nil
|
89
|
+
difference = (expected - actual).abs
|
90
|
+
message = Assertions.msg(message) {
|
91
|
+
"Expected |#{expected} - #{actual}| (#{difference}) to be within #{delta}"
|
92
|
+
}
|
93
|
+
assert delta >= difference, message
|
94
|
+
end
|
95
|
+
|
96
|
+
def refute_in_delta expected, actual, delta = 0.001, message = nil
|
97
|
+
difference = (expected - actual).abs
|
98
|
+
message = Assertions.msg(message) {
|
99
|
+
"Expected |#{expected} - #{actual}| (#{difference}) to not be within #{delta}"
|
100
|
+
}
|
101
|
+
refute delta >= difference, message
|
102
|
+
end
|
103
|
+
|
104
|
+
def assert_in_epsilon expected, actual, epsilon = 0.001, message = nil
|
105
|
+
assert_in_delta expected, actual, [expected.abs, actual.abs].min * epsilon, message
|
106
|
+
end
|
107
|
+
|
108
|
+
def refute_in_epsilon expected, actual, epsilon = 0.001, msg = nil
|
109
|
+
refute_in_delta expected, actual, expected * epsilon, msg
|
110
|
+
end
|
111
|
+
|
112
|
+
def assert_include? expected, actual, message = nil
|
113
|
+
message = Assertions.msg(message) {
|
114
|
+
"Expected #{Assertions.h(actual)} to include #{Assertions.h(expected)}"
|
115
|
+
}
|
116
|
+
assert_respond_to actual, :include?
|
117
|
+
assert actual.include?(expected), message
|
118
|
+
end
|
119
|
+
|
120
|
+
def refute_include? expected, actual, message = nil
|
121
|
+
message = Assertions.msg(message) {
|
122
|
+
"Expected #{Assertions.h(actual)} to not include #{Assertions.h(expected)}"
|
123
|
+
}
|
124
|
+
assert_respond_to actual, :include?
|
125
|
+
refute actual.include?(expected), message
|
126
|
+
end
|
127
|
+
|
128
|
+
def assert_instance_of expected, actual, message = nil
|
129
|
+
message = Assertions.msg(message) {
|
130
|
+
"Expected #{Assertions.h(actual)} to be an instance of #{expected}, not #{actual.class}"
|
131
|
+
}
|
132
|
+
assert actual.instance_of?(expected), message
|
133
|
+
end
|
134
|
+
|
135
|
+
def refute_instance_of expected, actual, message = nil
|
136
|
+
message = Assertions.msg(message) {
|
137
|
+
"Expected #{Assertions.h(actual)} to not be an instance of #{expected}"
|
138
|
+
}
|
139
|
+
refute actual.instance_of?(expected), message
|
140
|
+
end
|
141
|
+
|
142
|
+
def assert_kind_of expected, actual, message = nil
|
143
|
+
message = Assertions.msg(message) {
|
144
|
+
"Expected #{Assertions.h(actual)} to be a kind of #{expected}, not #{actual.class}"
|
145
|
+
}
|
146
|
+
assert actual.kind_of?(expected), message # standard:disable Style/ClassCheck
|
147
|
+
end
|
148
|
+
|
149
|
+
def refute_kind_of expected, actual, message = nil
|
150
|
+
message = Assertions.msg(message) {
|
151
|
+
"Expected #{Assertions.h(actual)} to not be a kind of #{expected}"
|
152
|
+
}
|
153
|
+
refute actual.kind_of?(expected), message # standard:disable Style/ClassCheck
|
154
|
+
end
|
155
|
+
|
156
|
+
def assert_match matcher, actual, message = nil
|
157
|
+
message = Assertions.msg(message) {
|
158
|
+
"Expected #{Assertions.h(actual)} to match #{Assertions.h(matcher)}"
|
159
|
+
}
|
160
|
+
assert_respond_to matcher, :=~
|
161
|
+
matcher = Regexp.new Regexp.escape matcher if String === matcher
|
162
|
+
assert matcher =~ actual, message
|
163
|
+
Regexp.last_match
|
164
|
+
end
|
165
|
+
|
166
|
+
def refute_match matcher, actual, message = nil
|
167
|
+
message = Assertions.msg(message) {
|
168
|
+
"Expected #{Assertions.h(actual)} to not match #{Assertions.h(matcher)}"
|
169
|
+
}
|
170
|
+
assert_respond_to matcher, :=~
|
171
|
+
refute matcher =~ actual, message
|
172
|
+
end
|
173
|
+
|
174
|
+
def assert_nil obj, message = nil
|
175
|
+
message = Assertions.msg(message) {
|
176
|
+
"Expected #{Assertions.h(obj)} to be nil"
|
177
|
+
}
|
178
|
+
|
179
|
+
assert obj.nil?, message
|
180
|
+
end
|
181
|
+
|
182
|
+
def refute_nil obj, message = nil
|
183
|
+
message = Assertions.msg(message) {
|
184
|
+
"Expected #{Assertions.h(obj)} to not be nil"
|
185
|
+
}
|
186
|
+
|
187
|
+
refute obj.nil?, message
|
188
|
+
end
|
189
|
+
|
190
|
+
def assert_operator left_operand, operator, right_operand, message = nil
|
191
|
+
message = Assertions.msg(message) {
|
192
|
+
"Expected #{Assertions.h(left_operand)} to be #{operator} #{Assertions.h(right_operand)}"
|
193
|
+
}
|
194
|
+
assert left_operand.__send__(operator, right_operand), message
|
195
|
+
end
|
196
|
+
|
197
|
+
def refute_operator left_operand, operator, right_operand, message = nil
|
198
|
+
message = Assertions.msg(message) {
|
199
|
+
"Expected #{Assertions.h(left_operand)} to not be #{operator} #{Assertions.h(right_operand)}"
|
200
|
+
}
|
201
|
+
refute left_operand.__send__(operator, right_operand), message
|
202
|
+
end
|
203
|
+
|
204
|
+
def assert_output expected_stdout, expected_stderr, message = nil, &block
|
205
|
+
assert_block "assert_output requires a block to capture output" unless block
|
206
|
+
|
207
|
+
actual_stdout, actual_stderr = Assertions.capture_io(&block)
|
208
|
+
|
209
|
+
if Regexp === expected_stderr
|
210
|
+
assert_match expected_stderr, actual_stderr, "In stderr"
|
211
|
+
elsif !expected_stderr.nil?
|
212
|
+
assert_equal expected_stderr, actual_stderr, "In stderr"
|
213
|
+
end
|
214
|
+
|
215
|
+
if Regexp === expected_stdout
|
216
|
+
assert_match expected_stdout, actual_stdout, "In stdout"
|
217
|
+
elsif !expected_stdout.nil?
|
218
|
+
assert_equal expected_stdout, actual_stdout, "In stdout"
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
def assert_path_exists path, message = nil
|
223
|
+
message = Assertions.msg(message) {
|
224
|
+
"Expected #{Assertions.h(path)} to exist"
|
225
|
+
}
|
226
|
+
|
227
|
+
assert File.exist?(path), message
|
228
|
+
end
|
229
|
+
|
230
|
+
def refute_path_exists path, message = nil
|
231
|
+
message = Assertions.msg(message) {
|
232
|
+
"Expected #{Assertions.h(path)} to not exist"
|
233
|
+
}
|
234
|
+
|
235
|
+
refute File.exist?(path), message
|
236
|
+
end
|
237
|
+
|
238
|
+
def assert_pattern message = nil
|
239
|
+
assert false, "assert_pattern requires a block to capture errors" unless block_given?
|
240
|
+
|
241
|
+
begin
|
242
|
+
yield
|
243
|
+
rescue NoMatchingPatternError => e
|
244
|
+
assert false, Assertions.msg(message) { "Expected pattern match: #{e.message}" }
|
245
|
+
end
|
246
|
+
end
|
247
|
+
|
248
|
+
def refute_pattern message = nil
|
249
|
+
assert false, "assert_pattern requires a block to capture errors" unless block_given?
|
250
|
+
|
251
|
+
begin
|
252
|
+
yield
|
253
|
+
refute true, Assertions.msg(message) { "Expected pattern not to match, but NoMatchingPatternError was raised" }
|
254
|
+
rescue NoMatchingPatternError
|
255
|
+
end
|
256
|
+
end
|
257
|
+
|
258
|
+
def assert_predicate obj, method, message = nil
|
259
|
+
message = Assertions.msg(message) {
|
260
|
+
"Expected #{Assertions.h(obj)} to be #{method}"
|
261
|
+
}
|
262
|
+
|
263
|
+
assert obj.send(method), message
|
264
|
+
end
|
265
|
+
|
266
|
+
def refute_predicate obj, method, message = nil
|
267
|
+
message = Assertions.msg(message) {
|
268
|
+
"Expected #{Assertions.h(obj)} to not be #{method}"
|
269
|
+
}
|
270
|
+
|
271
|
+
refute obj.send(method), message
|
272
|
+
end
|
273
|
+
|
274
|
+
def assert_raises *exp
|
275
|
+
assert false, "assert_raises requires a block to capture errors" unless block_given?
|
276
|
+
|
277
|
+
message = exp.pop if String === exp.last
|
278
|
+
exp << StandardError if exp.empty?
|
279
|
+
|
280
|
+
begin
|
281
|
+
yield
|
282
|
+
rescue Failure, Skip
|
283
|
+
raise
|
284
|
+
rescue *exp => e
|
285
|
+
return e
|
286
|
+
rescue SignalException, SystemExit
|
287
|
+
raise
|
288
|
+
rescue Exception => e # standard:disable Lint/RescueException
|
289
|
+
assert false, Assertions.msg(message) {
|
290
|
+
[
|
291
|
+
"#{Assertions.h(exp)} exception expected, not",
|
292
|
+
"Class: <#{e.class}>",
|
293
|
+
"Message: <#{e.message.inspect}>",
|
294
|
+
"---Backtrace---",
|
295
|
+
TLDR.filter_backtrace(e.backtrace).join("\n"),
|
296
|
+
"---------------"
|
297
|
+
].compact.join "\n"
|
298
|
+
}
|
299
|
+
end
|
300
|
+
|
301
|
+
exp = exp.first if exp.size == 1
|
302
|
+
|
303
|
+
assert false, "#{message}#{Assertions.h(exp)} expected but nothing was raised"
|
304
|
+
end
|
305
|
+
|
306
|
+
def assert_respond_to obj, method, message = nil
|
307
|
+
message = Assertions.msg(message) {
|
308
|
+
"Expected #{Assertions.h(obj)} (#{obj.class}) to respond to #{Assertions.h(method)}"
|
309
|
+
}
|
310
|
+
|
311
|
+
assert obj.respond_to?(method), message
|
312
|
+
end
|
313
|
+
|
314
|
+
def refute_respond_to obj, method, message = nil
|
315
|
+
message = Assertions.msg(message) {
|
316
|
+
"Expected #{Assertions.h(obj)} (#{obj.class}) to not respond to #{Assertions.h(method)}"
|
317
|
+
}
|
318
|
+
|
319
|
+
refute obj.respond_to?(method), message
|
320
|
+
end
|
321
|
+
|
322
|
+
def assert_same expected, actual, message = nil
|
323
|
+
message = Assertions.msg(message) {
|
324
|
+
<<~MSG
|
325
|
+
Expected objects to be the same, but weren't
|
326
|
+
Expected: #{Assertions.h(expected)} (oid=#{expected.object_id})
|
327
|
+
Actual: #{Assertions.h(actual)} (oid=#{actual.object_id})
|
328
|
+
MSG
|
329
|
+
}
|
330
|
+
assert expected.equal?(actual), message
|
331
|
+
end
|
332
|
+
|
333
|
+
def refute_same expected, actual, message = nil
|
334
|
+
message = Assertions.msg(message) {
|
335
|
+
"Expected #{Assertions.h(expected)} (oid=#{expected.object_id}) to not be the same as #{Assertions.h(actual)} (oid=#{actual.object_id})"
|
336
|
+
}
|
337
|
+
refute expected.equal?(actual), message
|
338
|
+
end
|
339
|
+
|
340
|
+
def assert_silent
|
341
|
+
assert_output "", "" do
|
342
|
+
yield
|
343
|
+
end
|
344
|
+
end
|
345
|
+
|
346
|
+
def assert_throws expected, message = nil
|
347
|
+
punchline = nil
|
348
|
+
caught = true
|
349
|
+
value = catch(expected) do
|
350
|
+
begin
|
351
|
+
yield
|
352
|
+
rescue ArgumentError => e
|
353
|
+
raise e unless e.message.include?("uncaught throw")
|
354
|
+
punchline = ", not #{e.message.split(" ").last}"
|
355
|
+
end
|
356
|
+
caught = false
|
357
|
+
end
|
358
|
+
|
359
|
+
assert caught, Assertions.msg(message) {
|
360
|
+
"Expected #{Assertions.h(expected)} to have been thrown#{punchline}"
|
361
|
+
}
|
362
|
+
value
|
363
|
+
end
|
364
|
+
end
|
365
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
class TLDR
|
2
|
+
class BacktraceFilter
|
3
|
+
BASE_PATH = __dir__.freeze
|
4
|
+
SORBET_RUNTIME_PATTERN = %r{sorbet-runtime.*[/\\]lib[/\\]types[/\\]private[/\\]}
|
5
|
+
CONCURRENT_RUBY_PATTERN = %r{concurrent-ruby.*[/\\]lib[/\\]concurrent-ruby[/\\]concurrent[/\\]}
|
6
|
+
|
7
|
+
def filter backtrace
|
8
|
+
return ["No backtrace"] unless backtrace
|
9
|
+
return backtrace.dup if $DEBUG
|
10
|
+
|
11
|
+
trim_leading_frames(backtrace) ||
|
12
|
+
trim_internal_frames(backtrace) ||
|
13
|
+
backtrace.dup
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def trim_leading_frames backtrace
|
19
|
+
if (trimmed = backtrace.take_while { |frame| meaningful? frame }).any?
|
20
|
+
trimmed
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def trim_internal_frames backtrace
|
25
|
+
if (trimmed = backtrace.select { |frame| meaningful? frame }).any?
|
26
|
+
trimmed
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def meaningful? frame
|
31
|
+
!internal? frame
|
32
|
+
end
|
33
|
+
|
34
|
+
def internal? frame
|
35
|
+
frame.start_with?(BASE_PATH) ||
|
36
|
+
frame.match?(SORBET_RUNTIME_PATTERN) ||
|
37
|
+
frame.match?(CONCURRENT_RUBY_PATTERN)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.filter_backtrace backtrace
|
42
|
+
BacktraceFilter.new.filter backtrace
|
43
|
+
end
|
44
|
+
end
|
data/lib/tldr/error.rb
ADDED
data/lib/tldr/planner.rb
ADDED
@@ -0,0 +1,170 @@
|
|
1
|
+
require "pathname"
|
2
|
+
|
3
|
+
class TLDR
|
4
|
+
class Planner
|
5
|
+
def plan config
|
6
|
+
search_locations = expand_search_locations config.paths
|
7
|
+
|
8
|
+
prepend_load_paths config
|
9
|
+
require_test_helper config
|
10
|
+
require_tests search_locations
|
11
|
+
|
12
|
+
tests = gather_tests
|
13
|
+
config.update_after_gathering_tests! tests
|
14
|
+
|
15
|
+
Plan.new prepend(
|
16
|
+
shuffle(
|
17
|
+
exclude_by_path(
|
18
|
+
exclude_by_name(
|
19
|
+
filter_by_line(
|
20
|
+
filter_by_name(tests, config.names),
|
21
|
+
search_locations
|
22
|
+
),
|
23
|
+
config.exclude_names
|
24
|
+
),
|
25
|
+
config.exclude_paths
|
26
|
+
),
|
27
|
+
config.seed
|
28
|
+
),
|
29
|
+
config
|
30
|
+
)
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
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
|
+
def gather_tests
|
51
|
+
gather_descendants(TLDR).flat_map { |subklass|
|
52
|
+
subklass.instance_methods.grep(/^test_/).sort.map { |method|
|
53
|
+
file, line = SorbetCompatibility.unwrap_method(subklass.instance_method(method)).source_location
|
54
|
+
Test.new subklass, method, file, line
|
55
|
+
}
|
56
|
+
}
|
57
|
+
end
|
58
|
+
|
59
|
+
def prepend tests, config
|
60
|
+
return tests if config.no_prepend
|
61
|
+
prepended_locations = expand_search_locations expand_globs config.prepend_tests
|
62
|
+
prepended, rest = tests.partition { |test|
|
63
|
+
locations_include_test? prepended_locations, test
|
64
|
+
}
|
65
|
+
prepended + rest
|
66
|
+
end
|
67
|
+
|
68
|
+
def shuffle tests, seed
|
69
|
+
tests.shuffle(random: Random.new(seed))
|
70
|
+
end
|
71
|
+
|
72
|
+
def exclude_by_path tests, exclude_paths
|
73
|
+
excluded_locations = expand_search_locations expand_globs exclude_paths
|
74
|
+
return tests if excluded_locations.empty?
|
75
|
+
|
76
|
+
tests.reject { |test|
|
77
|
+
locations_include_test? excluded_locations, test
|
78
|
+
}
|
79
|
+
end
|
80
|
+
|
81
|
+
def exclude_by_name tests, exclude_names
|
82
|
+
return tests if exclude_names.empty?
|
83
|
+
|
84
|
+
name_excludes = expand_names_with_patterns exclude_names
|
85
|
+
|
86
|
+
tests.reject { |test|
|
87
|
+
name_excludes.any? { |filter|
|
88
|
+
filter === test.method.to_s || filter === "#{test.klass}##{test.method}"
|
89
|
+
}
|
90
|
+
}
|
91
|
+
end
|
92
|
+
|
93
|
+
def filter_by_line tests, search_locations
|
94
|
+
line_specific_locations = search_locations.reject { |location| location.line.nil? }
|
95
|
+
return tests if line_specific_locations.empty?
|
96
|
+
|
97
|
+
tests.select { |test|
|
98
|
+
locations_include_test? line_specific_locations, test
|
99
|
+
}
|
100
|
+
end
|
101
|
+
|
102
|
+
def filter_by_name tests, names
|
103
|
+
return tests if names.empty?
|
104
|
+
|
105
|
+
name_filters = expand_names_with_patterns names
|
106
|
+
|
107
|
+
tests.select { |test|
|
108
|
+
name_filters.any? { |filter|
|
109
|
+
filter === test.method.to_s || filter === "#{test.klass}##{test.method}"
|
110
|
+
}
|
111
|
+
}
|
112
|
+
end
|
113
|
+
|
114
|
+
def prepend_load_paths config
|
115
|
+
config.load_paths.each do |load_path|
|
116
|
+
$LOAD_PATH.unshift File.expand_path(load_path, Dir.pwd)
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
def require_test_helper config
|
121
|
+
return if config.no_helper || config.helper.nil? || !File.exist?(config.helper)
|
122
|
+
require File.expand_path(config.helper, Dir.pwd)
|
123
|
+
end
|
124
|
+
|
125
|
+
def require_tests search_locations
|
126
|
+
search_locations.each do |location|
|
127
|
+
require location.file
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
def gather_descendants root_klass
|
132
|
+
root_klass.subclasses + root_klass.subclasses.flat_map { |subklass|
|
133
|
+
gather_descendants subklass
|
134
|
+
}
|
135
|
+
end
|
136
|
+
|
137
|
+
def locations_include_test? locations, test
|
138
|
+
locations.any? { |location|
|
139
|
+
location.file == test.file && (location.line.nil? || test.covers_line?(location.line))
|
140
|
+
}
|
141
|
+
end
|
142
|
+
|
143
|
+
# Because search paths to TLDR can include line numbers (e.g. a.rb:4), we
|
144
|
+
# can't just pass everything to Dir.glob. Instead, we have to check whether
|
145
|
+
# a user-provided search path looks like a glob, and if so, expand it
|
146
|
+
#
|
147
|
+
# Globby characters specified here:
|
148
|
+
# https://ruby-doc.org/3.2.2/Dir.html#method-c-glob
|
149
|
+
def expand_globs search_paths
|
150
|
+
search_paths.flat_map { |search_path|
|
151
|
+
if search_path.match?(/[*?\[\]{}]/)
|
152
|
+
raise Error, "Can't combine globs and line numbers in: #{search_path}" if search_path.match?(/:(\d+)$/)
|
153
|
+
Dir[search_path]
|
154
|
+
else
|
155
|
+
search_path
|
156
|
+
end
|
157
|
+
}
|
158
|
+
end
|
159
|
+
|
160
|
+
def expand_names_with_patterns names
|
161
|
+
names.map { |name|
|
162
|
+
if name.is_a?(String) && name =~ /^\/(.*)\/$/
|
163
|
+
Regexp.new $1
|
164
|
+
else
|
165
|
+
name
|
166
|
+
end
|
167
|
+
}
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
class TLDR
|
2
|
+
module Reporters
|
3
|
+
class Base
|
4
|
+
def initialize(config, out = $stdout, err = $stderr)
|
5
|
+
out.sync = true
|
6
|
+
err.sync = true
|
7
|
+
|
8
|
+
@config = config
|
9
|
+
@out = out
|
10
|
+
@err = err
|
11
|
+
end
|
12
|
+
|
13
|
+
# Will be called before any tests are run
|
14
|
+
def before_suite tests
|
15
|
+
end
|
16
|
+
|
17
|
+
# Will be called after each test, unless the run has already been aborted
|
18
|
+
def after_test test_result
|
19
|
+
end
|
20
|
+
|
21
|
+
# Will be called after all tests have run, unless the run was aborted
|
22
|
+
#
|
23
|
+
# Exactly ONE of `after_suite`, `after_tldr`, or `after_fail_fast` will be called
|
24
|
+
def after_suite test_results
|
25
|
+
end
|
26
|
+
|
27
|
+
# Called after the suite-wide time limit expires and the run is aborted
|
28
|
+
def after_tldr planned_tests, wip_tests, test_results
|
29
|
+
end
|
30
|
+
|
31
|
+
# Called after the first test fails when --fail-fast is enabled, aborting the run
|
32
|
+
def after_fail_fast planned_tests, wip_tests, test_results, last_result
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|