ruptr 0.1.3
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/bin/ruptr +10 -0
- data/lib/ruptr/adapters/assertions.rb +43 -0
- data/lib/ruptr/adapters/rr.rb +23 -0
- data/lib/ruptr/adapters/rspec_expect.rb +32 -0
- data/lib/ruptr/adapters/rspec_mocks.rb +29 -0
- data/lib/ruptr/adapters.rb +7 -0
- data/lib/ruptr/assertions.rb +493 -0
- data/lib/ruptr/autorun.rb +38 -0
- data/lib/ruptr/capture_output.rb +106 -0
- data/lib/ruptr/compat.rb +27 -0
- data/lib/ruptr/exceptions.rb +47 -0
- data/lib/ruptr/formatter.rb +78 -0
- data/lib/ruptr/golden_master.rb +143 -0
- data/lib/ruptr/instance.rb +37 -0
- data/lib/ruptr/main.rb +439 -0
- data/lib/ruptr/minitest/override.rb +4 -0
- data/lib/ruptr/minitest.rb +134 -0
- data/lib/ruptr/plain.rb +425 -0
- data/lib/ruptr/progress.rb +98 -0
- data/lib/ruptr/rake_task.rb +18 -0
- data/lib/ruptr/report.rb +104 -0
- data/lib/ruptr/result.rb +38 -0
- data/lib/ruptr/rspec/configuration.rb +191 -0
- data/lib/ruptr/rspec/example_group.rb +498 -0
- data/lib/ruptr/rspec/override.rb +4 -0
- data/lib/ruptr/rspec.rb +211 -0
- data/lib/ruptr/runner.rb +433 -0
- data/lib/ruptr/sink.rb +58 -0
- data/lib/ruptr/stringified.rb +57 -0
- data/lib/ruptr/suite.rb +188 -0
- data/lib/ruptr/surrogate_exception.rb +71 -0
- data/lib/ruptr/tabular.rb +21 -0
- data/lib/ruptr/tap.rb +51 -0
- data/lib/ruptr/testunit/override.rb +4 -0
- data/lib/ruptr/testunit.rb +117 -0
- data/lib/ruptr/timing_cache.rb +100 -0
- data/lib/ruptr/tty_colors.rb +60 -0
- data/lib/ruptr/utils.rb +57 -0
- metadata +77 -0
data/lib/ruptr/main.rb
ADDED
|
@@ -0,0 +1,439 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'optparse'
|
|
4
|
+
require 'pathname'
|
|
5
|
+
|
|
6
|
+
require_relative 'suite'
|
|
7
|
+
require_relative 'utils'
|
|
8
|
+
require_relative 'runner'
|
|
9
|
+
require_relative 'timing_cache'
|
|
10
|
+
require_relative 'golden_master'
|
|
11
|
+
require_relative 'report'
|
|
12
|
+
require_relative 'sink'
|
|
13
|
+
require_relative 'progress'
|
|
14
|
+
require_relative 'formatter'
|
|
15
|
+
require_relative 'tabular'
|
|
16
|
+
require_relative 'tap'
|
|
17
|
+
|
|
18
|
+
require_relative 'minitest'
|
|
19
|
+
require_relative 'testunit'
|
|
20
|
+
require_relative 'rspec'
|
|
21
|
+
|
|
22
|
+
module Ruptr
|
|
23
|
+
class Main
|
|
24
|
+
DEFAULT_STATE_DIRNAME = '.ruptr-state'
|
|
25
|
+
DEFAULT_PROJECT_LOAD_PATHS = %w[lib].freeze
|
|
26
|
+
|
|
27
|
+
def initialize(project_dir = '.')
|
|
28
|
+
@project_path = Pathname(project_dir)
|
|
29
|
+
@extra_load_paths = []
|
|
30
|
+
@extra_requires = []
|
|
31
|
+
@add_default_project_load_paths = true
|
|
32
|
+
@warnings = $VERBOSE
|
|
33
|
+
@verbosity = 0
|
|
34
|
+
@capture_output = true
|
|
35
|
+
@monkey_patch = false
|
|
36
|
+
@formatter_name = nil
|
|
37
|
+
@runner_name = nil
|
|
38
|
+
@pager_mode = true
|
|
39
|
+
@pager_only_on_problem = true
|
|
40
|
+
@output_path = nil
|
|
41
|
+
@include_names = []
|
|
42
|
+
@exclude_names = []
|
|
43
|
+
@include_tags = []
|
|
44
|
+
@exclude_tags = []
|
|
45
|
+
@include_tags_values = {}
|
|
46
|
+
@exclude_tags_values = {}
|
|
47
|
+
@only_test_files = []
|
|
48
|
+
@operations = []
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
attr_accessor :extra_load_paths,
|
|
52
|
+
:extra_requires,
|
|
53
|
+
:warnings,
|
|
54
|
+
:verbosity,
|
|
55
|
+
:capture_output,
|
|
56
|
+
:monkey_patch,
|
|
57
|
+
:parallel_jobs,
|
|
58
|
+
:output_path,
|
|
59
|
+
:only_test_files
|
|
60
|
+
|
|
61
|
+
def load_all_tests? = @only_test_files.empty?
|
|
62
|
+
|
|
63
|
+
def parse_options(argv)
|
|
64
|
+
argv = argv.dup
|
|
65
|
+
OptionParser.new do |op|
|
|
66
|
+
op.on('-C', '--project-path=PATH') { |s| @project_path = Pathname(s) }
|
|
67
|
+
op.on('--state-directory=PATH') { |s| @state_path = Pathname(s) }
|
|
68
|
+
op.on('--[no-]default-project-load-paths') { |v| @add_default_project_load_paths = v }
|
|
69
|
+
op.on('-I', '--include=PATH') { |s| @extra_load_paths << s }
|
|
70
|
+
op.on('-r', '--require=PATH') { |s| @extra_requires << s }
|
|
71
|
+
op.on('--[no-]capture-output') { |v| @capture_output = v }
|
|
72
|
+
op.on('-m', '--[no-]monkey-patch') { |v| @monkey_patch = v }
|
|
73
|
+
op.on('-w', '--[no-]warnings') { |v| @warnings = v ? true : nil }
|
|
74
|
+
op.on('-o', '--output=PATH') { |s| @output_path = Pathname(s) }
|
|
75
|
+
op.on('-f', '--formatter=NAME') { |s| @formatter_name = s }
|
|
76
|
+
op.on('--[no-]pager') { |v| @pager_mode = v }
|
|
77
|
+
op.on('--[no-]pager-only-on-problem') { |v| @pager_only_on_problem = v }
|
|
78
|
+
op.on('--runner=NAME') { |s| @runner_name = s }
|
|
79
|
+
op.on('-j', '--jobs=N') do |s|
|
|
80
|
+
n = s.to_i
|
|
81
|
+
n = Runner::Parallel.default_parallel_jobs unless n.positive?
|
|
82
|
+
@parallel_jobs = n
|
|
83
|
+
end
|
|
84
|
+
op.on('-e', '--example=STRING') { |s| @include_names << Regexp.new(Regexp.quote(s)) }
|
|
85
|
+
op.on('-E', '--example-matches=REGEXP') { |s| @include_names << Regexp.new(s) }
|
|
86
|
+
op.on('-t', '--tag=TAG[:VALUE]') do |s|
|
|
87
|
+
name, value = s.split(':', 2)
|
|
88
|
+
name.delete_prefix!('~') if (exclude = name.start_with?('~'))
|
|
89
|
+
name = name.to_sym
|
|
90
|
+
if value
|
|
91
|
+
value = value.delete_prefix(':').to_sym if value.start_with?(':')
|
|
92
|
+
(exclude ? @exclude_tags_values : @include_tags_values)[name] = value
|
|
93
|
+
else
|
|
94
|
+
(exclude ? @exclude_tags : @include_tags) << name
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
op.on('-q', '--[no-]quiet') { |v| @verbosity -= v ? 1 : 0 }
|
|
98
|
+
op.on('-v', '--[no-]verbose') { |v| @verbosity += v ? 1 : 0 }
|
|
99
|
+
op.on('--golden-accept') { @operations << :golden_accept }
|
|
100
|
+
op.on('--show-test-suite') { @operations << :show_test_suite }
|
|
101
|
+
end.order!(argv)
|
|
102
|
+
@only_test_files = argv.dup
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def state_path
|
|
106
|
+
@state_path ||= @project_path / Pathname(DEFAULT_STATE_DIRNAME)
|
|
107
|
+
unless @state_path_made
|
|
108
|
+
@state_path.mkpath
|
|
109
|
+
@state_path_made = true
|
|
110
|
+
end
|
|
111
|
+
@state_path
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def with_state_directory_lock
|
|
115
|
+
(state_path / "lock").open(File::CREAT | File::RDWR) do |io|
|
|
116
|
+
io.flock(File::LOCK_EX | File::LOCK_NB) or fail "state directory locked"
|
|
117
|
+
yield
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def compat_layers
|
|
122
|
+
@compat_layers ||= Compat.subclasses.map(&:new)
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def project_load_paths
|
|
126
|
+
@project_load_paths ||=
|
|
127
|
+
(@extra_load_paths +
|
|
128
|
+
if @add_default_project_load_paths
|
|
129
|
+
DEFAULT_PROJECT_LOAD_PATHS.map { |path| (@project_path / path).to_s } +
|
|
130
|
+
compat_layers.flat_map(&:default_project_load_paths)
|
|
131
|
+
else
|
|
132
|
+
[]
|
|
133
|
+
end).uniq
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def each_test_file(&)
|
|
137
|
+
if load_all_tests?
|
|
138
|
+
compat_layers.each { |compat| compat.each_default_project_test_file(@project_path, &) }
|
|
139
|
+
else
|
|
140
|
+
@only_test_files.each(&)
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
private def prepare_compat
|
|
145
|
+
compat_layers.each do |compat|
|
|
146
|
+
compat.global_install!
|
|
147
|
+
compat.global_monkey_patch! if @monkey_patch
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
private def finalize_compat
|
|
152
|
+
compat_layers.each(&:finalize_configuration!)
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
private def load_test_files
|
|
156
|
+
return if @test_files_loaded
|
|
157
|
+
prepare_compat
|
|
158
|
+
@load_user_time, @load_system_time, @load_real_time = Ruptr.measure_processor_and_real_time do
|
|
159
|
+
$LOAD_PATH.unshift(*project_load_paths)
|
|
160
|
+
@extra_requires.each { |name| require(name) }
|
|
161
|
+
each_test_file do |path|
|
|
162
|
+
path = path.to_s
|
|
163
|
+
require(%r{\A\.{0,2}/}.match?(path) ? path : "./#{path}")
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
finalize_compat
|
|
167
|
+
@test_files_loaded = true
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
def loaded_test_suite
|
|
171
|
+
@loaded_test_suite ||= TestSuite.new.tap do |ts|
|
|
172
|
+
load_test_files
|
|
173
|
+
@total_loaded_test_cases_before_internal_filtering = 0
|
|
174
|
+
compat_layers.each do |compat|
|
|
175
|
+
tg = compat.adapted_test_group
|
|
176
|
+
@total_loaded_test_cases_before_internal_filtering += tg.count_test_cases
|
|
177
|
+
ts.add_test_subgroup(compat.filter_test_group(tg))
|
|
178
|
+
end
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
def filtered_test_suite
|
|
183
|
+
@filtered_test_suite ||=
|
|
184
|
+
if @include_names.empty? && @exclude_names.empty? &&
|
|
185
|
+
@include_tags.empty? && @exclude_tags.empty? &&
|
|
186
|
+
@include_tags_values.empty? && @exclude_tags_values.empty?
|
|
187
|
+
loaded_test_suite
|
|
188
|
+
else
|
|
189
|
+
loaded_test_suite.filter_test_cases_recursive do |tc|
|
|
190
|
+
(@include_names.empty? || @include_names.any? { |v| v === tc.description }) &&
|
|
191
|
+
(@exclude_names.empty? || @exclude_names.none? { |v| v === tc.description }) &&
|
|
192
|
+
(@include_tags.empty? || @include_tags.any? { |k| tc.tags.include?(k) }) &&
|
|
193
|
+
(@exclude_tags.empty? || @exclude_tags.none? { |k| tc.tags.include?(k) }) &&
|
|
194
|
+
(@include_tags_values.empty? || @include_tags_values.any? { |k, v| v === tc.tags[k] }) &&
|
|
195
|
+
(@exclude_tags_values.empty? || @exclude_tags_values.none? { |k, v| v === tc.tags[k] })
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
def report_total_filtered
|
|
201
|
+
return if @verbosity.negative?
|
|
202
|
+
loaded_test_suite
|
|
203
|
+
n = @total_loaded_test_cases_before_internal_filtering
|
|
204
|
+
m = filtered_test_suite.count_test_cases
|
|
205
|
+
$stderr.puts "#{n - m} test cases filtered" unless n == m
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
def pager_mode?
|
|
209
|
+
@pager_mode && !@output_path && $stdout.tty?
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
private def open_output
|
|
213
|
+
fail if @output_file
|
|
214
|
+
if pager_mode? && (!@pager_only_on_problem || test_suite_problem?)
|
|
215
|
+
@output_file = IO.popen(ENV['PAGER'] || 'more', 'w',
|
|
216
|
+
external_encoding: $stdout.external_encoding)
|
|
217
|
+
@output_file_close = true
|
|
218
|
+
else
|
|
219
|
+
if @output_path
|
|
220
|
+
@output_file = @output_path.open('w')
|
|
221
|
+
@output_file_close = true
|
|
222
|
+
else
|
|
223
|
+
@output_file = $stdout
|
|
224
|
+
@output_file_close = false
|
|
225
|
+
end
|
|
226
|
+
end
|
|
227
|
+
@output_file
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
private def close_output
|
|
231
|
+
@output_file.close if @output_file_close
|
|
232
|
+
@output_file = @output_file_close = nil
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
def formatter
|
|
236
|
+
@formatter ||= begin
|
|
237
|
+
formatter_class = if @formatter_name
|
|
238
|
+
Formatter.find_formatter(@formatter_name.to_sym) or fail "unknown formatter: #{@formatter_name}"
|
|
239
|
+
else
|
|
240
|
+
Formatter.class_from_env
|
|
241
|
+
end
|
|
242
|
+
opts = {}
|
|
243
|
+
opts[:verbosity] = @verbosity if formatter_class.include?(Formatter::Verbosity)
|
|
244
|
+
output = open_output
|
|
245
|
+
if formatter_class.include?(Formatter::Colorizing)
|
|
246
|
+
opts[:colorizer] = TTYColors.for(pager_mode? ? $stdout : output)
|
|
247
|
+
end
|
|
248
|
+
opts = Formatter.opts_from_env(formatter_class, **opts)
|
|
249
|
+
formatter_class.new(output, **opts)
|
|
250
|
+
end
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
def timing_cache
|
|
254
|
+
@timing_cache ||= TimingCache.new(state_path, filtered_test_suite)
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
def golden_master
|
|
258
|
+
@golden_master ||= GoldenMaster.new(
|
|
259
|
+
state_path,
|
|
260
|
+
original_test_suite: load_all_tests? ? loaded_test_suite : nil,
|
|
261
|
+
filtered_test_suite: filtered_test_suite,
|
|
262
|
+
)
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
def runner
|
|
266
|
+
@runner ||= begin
|
|
267
|
+
runner_class = if @runner_name
|
|
268
|
+
Runner.find_runner(@runner_name.to_sym) or fail "unknown runner: #{@runner_name}"
|
|
269
|
+
else
|
|
270
|
+
Runner.class_from_env(default_parallel: @parallel_jobs != 1)
|
|
271
|
+
end
|
|
272
|
+
opts = {
|
|
273
|
+
timing_store: timing_cache.timing_store,
|
|
274
|
+
golden_store: golden_master.golden_store,
|
|
275
|
+
capture_output: @capture_output,
|
|
276
|
+
}
|
|
277
|
+
opts[:parallel_jobs] = @parallel_jobs if !@parallel_jobs.nil? && runner_class <= Runner::Parallel
|
|
278
|
+
opts = Runner.opts_from_env(runner_class, **opts)
|
|
279
|
+
runner_class.new(**opts)
|
|
280
|
+
end
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
def warmup
|
|
284
|
+
Process.warmup if Process.respond_to?(:warmup)
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
def test_suite_passed?
|
|
288
|
+
@report ? @report.passed? : @sink_passed.passed?
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
def test_suite_problem?
|
|
292
|
+
if @report
|
|
293
|
+
@report.failed? || @report.each_test_case_result.any? { |_, tr| tr.captured_stderr? }
|
|
294
|
+
else
|
|
295
|
+
!test_suite_passed?
|
|
296
|
+
end
|
|
297
|
+
end
|
|
298
|
+
|
|
299
|
+
private def sink
|
|
300
|
+
@sink ||= begin
|
|
301
|
+
sinks = []
|
|
302
|
+
if pager_mode?
|
|
303
|
+
sinks << Report::Builder.new((@report = Report.new))
|
|
304
|
+
sinks << Progress::StatusLine.new($stderr) if !@verbosity.negative? && $stderr.tty?
|
|
305
|
+
else
|
|
306
|
+
sinks << formatter
|
|
307
|
+
sinks << (@sink_passed = Sink::Passed.new)
|
|
308
|
+
end
|
|
309
|
+
sinks << timing_cache.timing_store
|
|
310
|
+
Sink::Tee.for(sinks)
|
|
311
|
+
end
|
|
312
|
+
end
|
|
313
|
+
|
|
314
|
+
private def plan_header
|
|
315
|
+
header = {
|
|
316
|
+
planned_test_case_count: filtered_test_suite.count_test_cases,
|
|
317
|
+
}
|
|
318
|
+
sink.begin_plan(header)
|
|
319
|
+
end
|
|
320
|
+
|
|
321
|
+
private def plan_footer
|
|
322
|
+
footer = {
|
|
323
|
+
test_files_load_user_time: @load_user_time,
|
|
324
|
+
test_files_load_system_time: @load_system_time,
|
|
325
|
+
test_files_load_real_time: @load_real_time,
|
|
326
|
+
test_suite_run_user_time: @run_user_time,
|
|
327
|
+
test_suite_run_system_time: @run_system_time,
|
|
328
|
+
test_suite_run_real_time: @run_real_time,
|
|
329
|
+
overall_user_time: @overall_user_time,
|
|
330
|
+
overall_system_time: @overall_system_time,
|
|
331
|
+
overall_real_time: @overall_real_time,
|
|
332
|
+
}
|
|
333
|
+
sink.finish_plan(footer)
|
|
334
|
+
end
|
|
335
|
+
|
|
336
|
+
private def speedup_capture_output(&)
|
|
337
|
+
return yield unless @capture_output
|
|
338
|
+
CaptureOutput.fixed_install!(&)
|
|
339
|
+
end
|
|
340
|
+
|
|
341
|
+
private def run_test_suite
|
|
342
|
+
speedup_capture_output do
|
|
343
|
+
@run_real_time = Ruptr.measure_real_time do
|
|
344
|
+
@run_user_time, @run_system_time = Ruptr.measure_processor_time do
|
|
345
|
+
runner.dispatch(filtered_test_suite, sink)
|
|
346
|
+
end
|
|
347
|
+
end
|
|
348
|
+
end
|
|
349
|
+
end
|
|
350
|
+
|
|
351
|
+
private def save_timing_cache
|
|
352
|
+
@timing_cache&.save!(replace: load_all_tests? && loaded_test_suite.equal?(filtered_test_suite))
|
|
353
|
+
end
|
|
354
|
+
|
|
355
|
+
private def save_golden_master
|
|
356
|
+
@golden_master&.save_trial!
|
|
357
|
+
end
|
|
358
|
+
|
|
359
|
+
private def save_bookkeeping
|
|
360
|
+
save_timing_cache
|
|
361
|
+
save_golden_master
|
|
362
|
+
end
|
|
363
|
+
|
|
364
|
+
def output_report
|
|
365
|
+
return unless @report
|
|
366
|
+
@report.emit(formatter)
|
|
367
|
+
rescue Errno::EPIPE
|
|
368
|
+
end
|
|
369
|
+
|
|
370
|
+
private def possibly_with_warnings
|
|
371
|
+
saved = $VERBOSE
|
|
372
|
+
$VERBOSE = @warnings
|
|
373
|
+
yield
|
|
374
|
+
ensure
|
|
375
|
+
$VERBOSE = saved
|
|
376
|
+
end
|
|
377
|
+
|
|
378
|
+
private def measure_overall_time(&)
|
|
379
|
+
@overall_user_time, @overall_system_time, @overall_real_time =
|
|
380
|
+
Ruptr.measure_processor_and_real_time(&)
|
|
381
|
+
end
|
|
382
|
+
|
|
383
|
+
private def run_tests
|
|
384
|
+
possibly_with_warnings do
|
|
385
|
+
measure_overall_time do
|
|
386
|
+
plan_header
|
|
387
|
+
report_total_filtered
|
|
388
|
+
warmup
|
|
389
|
+
with_state_directory_lock do
|
|
390
|
+
run_test_suite
|
|
391
|
+
save_bookkeeping
|
|
392
|
+
end
|
|
393
|
+
end
|
|
394
|
+
plan_footer
|
|
395
|
+
output_report
|
|
396
|
+
ensure
|
|
397
|
+
close_output
|
|
398
|
+
end
|
|
399
|
+
end
|
|
400
|
+
|
|
401
|
+
private def golden_accept
|
|
402
|
+
golden_master.accept_trial!
|
|
403
|
+
end
|
|
404
|
+
|
|
405
|
+
private def show_test_suite
|
|
406
|
+
report_total_filtered
|
|
407
|
+
|
|
408
|
+
io = $stdout
|
|
409
|
+
w1, w2 = 1, 1
|
|
410
|
+
traverse = lambda do |te, prefix, last|
|
|
411
|
+
io << prefix << (last ? '└' : '├') << '─' * w1 if prefix
|
|
412
|
+
io << (te.test_group? && !te.empty? ? (w2.zero? ? '┮' : '┬') : (w2.zero? ? '╼' : '─'))
|
|
413
|
+
io << '─' * w2.pred << '╼' unless w2.zero?
|
|
414
|
+
io << ' ' << (te.label || '...') << "\n"
|
|
415
|
+
if te.test_group?
|
|
416
|
+
prefix = prefix ? prefix + (last ? ' ' : '│') + ' ' * w1 : ''
|
|
417
|
+
last_te = nil
|
|
418
|
+
te.each_test_element do |te|
|
|
419
|
+
traverse.call(last_te, prefix, false) if last_te
|
|
420
|
+
last_te = te
|
|
421
|
+
end
|
|
422
|
+
traverse.call(last_te, prefix, true) if last_te
|
|
423
|
+
end
|
|
424
|
+
end
|
|
425
|
+
traverse.call(filtered_test_suite, nil, true)
|
|
426
|
+
end
|
|
427
|
+
|
|
428
|
+
def run
|
|
429
|
+
# NOTE: this method's return value is passed Kernel#exit
|
|
430
|
+
if @operations.empty?
|
|
431
|
+
run_tests
|
|
432
|
+
test_suite_passed?
|
|
433
|
+
else
|
|
434
|
+
@operations.each { |operation_name| send(operation_name) }
|
|
435
|
+
true
|
|
436
|
+
end
|
|
437
|
+
end
|
|
438
|
+
end
|
|
439
|
+
end
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'suite'
|
|
4
|
+
require_relative 'compat'
|
|
5
|
+
require_relative 'autorun'
|
|
6
|
+
require_relative 'adapters/assertions'
|
|
7
|
+
|
|
8
|
+
module Ruptr
|
|
9
|
+
class Compat
|
|
10
|
+
class Minitest < self
|
|
11
|
+
def default_project_load_paths = %w[test]
|
|
12
|
+
|
|
13
|
+
def default_project_test_globs = %w[test/**/*_test.rb test/**/test_*.rb]
|
|
14
|
+
|
|
15
|
+
def global_install!
|
|
16
|
+
if Object.const_defined?(:Minitest)
|
|
17
|
+
return if Object.const_get(:Minitest) == @adapter_module
|
|
18
|
+
fail "minitest already loaded!"
|
|
19
|
+
end
|
|
20
|
+
Object.const_set(:Minitest, adapter_module)
|
|
21
|
+
this = self
|
|
22
|
+
m = Module.new do
|
|
23
|
+
define_method(:require) do |name|
|
|
24
|
+
name = name.to_path unless name.is_a?(String)
|
|
25
|
+
case name
|
|
26
|
+
when 'minitest', 'minitest/test', 'minitest/proveit', 'minitest/hooks'
|
|
27
|
+
return
|
|
28
|
+
when 'minitest/autorun'
|
|
29
|
+
this.schedule_autorun!
|
|
30
|
+
return
|
|
31
|
+
when 'minitest/stub_const'
|
|
32
|
+
nil
|
|
33
|
+
else
|
|
34
|
+
fail "#{self.class.name}: unknown minitest library: #{name}" if name.start_with?('minitest/')
|
|
35
|
+
end
|
|
36
|
+
super(name)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
Kernel.prepend(m)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def adapter_module
|
|
43
|
+
@adapter_module ||= Module.new do
|
|
44
|
+
def self.def_module(name, &) = const_set(name, Module.new(&))
|
|
45
|
+
def self.def_class(name, &) = const_set(name, Class.new(&))
|
|
46
|
+
|
|
47
|
+
const_set(:Assertion, Ruptr::Assertions::AssertionError)
|
|
48
|
+
|
|
49
|
+
def_module(:Assertions) do
|
|
50
|
+
include Adapters::RuptrAssertions
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def_module(:Hooks) do
|
|
54
|
+
# TODO: #around_all/#before_all/#after_all
|
|
55
|
+
|
|
56
|
+
def around = yield
|
|
57
|
+
|
|
58
|
+
def ruptr_wrap_test_instance
|
|
59
|
+
ran = false
|
|
60
|
+
super do
|
|
61
|
+
around do
|
|
62
|
+
yield
|
|
63
|
+
ran = true
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
raise SkippedException unless ran
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
adapter_module = self
|
|
71
|
+
|
|
72
|
+
def_class(:Test) do
|
|
73
|
+
def self.parallelize_me! = nil
|
|
74
|
+
|
|
75
|
+
def self.prove_it? = false
|
|
76
|
+
def self.prove_it! = define_singleton_method(:prove_it?) { true }
|
|
77
|
+
|
|
78
|
+
def name
|
|
79
|
+
@test_method_name
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def name=(v)
|
|
83
|
+
@test_method_name = v
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def prove_it
|
|
87
|
+
flunk("Prove it!") if self.class.prove_it? && ruptr_assertions_count.zero?
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def setup = nil
|
|
91
|
+
def teardown = nil
|
|
92
|
+
|
|
93
|
+
include TestInstance
|
|
94
|
+
include adapter_module::Assertions
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
private def make_run_block(klass, method_name)
|
|
100
|
+
lambda do |context|
|
|
101
|
+
inst = klass.new
|
|
102
|
+
inst.name = method_name
|
|
103
|
+
inst.ruptr_initialize_test_instance(context)
|
|
104
|
+
inst.ruptr_wrap_test_instance do
|
|
105
|
+
inst.setup
|
|
106
|
+
inst.public_send(method_name)
|
|
107
|
+
inst.prove_it
|
|
108
|
+
ensure
|
|
109
|
+
inst.teardown
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def adapted_test_group
|
|
115
|
+
# TODO: Test descriptions should be "<class_name>#<method_name>"?
|
|
116
|
+
traverse = lambda do |klass|
|
|
117
|
+
root = klass.equal?(adapter_module::Test)
|
|
118
|
+
TestGroup.new(root ? "[Minitest]" : klass.name,
|
|
119
|
+
identifier: root ? :minitest : klass.name).tap do |tg|
|
|
120
|
+
klass.public_instance_methods(true)
|
|
121
|
+
.filter { |sym| sym.start_with?('test_') }.each do |test_method_name|
|
|
122
|
+
tc = TestCase.new(test_method_name.to_s, &make_run_block(klass, test_method_name))
|
|
123
|
+
tg.add_test_case(tc)
|
|
124
|
+
end
|
|
125
|
+
klass.subclasses.each do |subklass|
|
|
126
|
+
tg.add_test_subgroup(traverse.call(subklass))
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
traverse.call(adapter_module::Test)
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
end
|