tldr 0.1.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 +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
|