turbo_tests2 3.0.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
- checksums.yaml.gz.sig +0 -0
- data/CHANGELOG.md +46 -0
- data/CITATION.cff +20 -0
- data/CODE_OF_CONDUCT.md +134 -0
- data/CONTRIBUTING.md +235 -0
- data/FUNDING.md +74 -0
- data/README.md +694 -0
- data/RUBOCOP.md +71 -0
- data/SECURITY.md +21 -0
- data/certs/pboling.pem +27 -0
- data/exe/turbo_tests2 +7 -0
- data/lib/turbo_tests/cli.rb +228 -0
- data/lib/turbo_tests/json_rows_formatter.rb +171 -0
- data/lib/turbo_tests/reporter.rb +179 -0
- data/lib/turbo_tests/runner.rb +361 -0
- data/lib/turbo_tests/shim.rb +75 -0
- data/lib/turbo_tests/version.rb +8 -0
- data/lib/turbo_tests.rb +110 -0
- data/lib/turbo_tests2/rspec/shared_contexts/simplecov_spawn.rb +26 -0
- data/lib/turbo_tests2.rb +6 -0
- data/lib/utils/hash_extension.rb +7 -0
- data/sig/turbo_tests/version.rbs +7 -0
- data.tar.gz.sig +1 -0
- metadata +344 -0
- metadata.gz.sig +0 -0
|
@@ -0,0 +1,361 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
require "parallel_tests/rspec/runner"
|
|
5
|
+
|
|
6
|
+
require_relative "../utils/hash_extension"
|
|
7
|
+
|
|
8
|
+
module TurboTests
|
|
9
|
+
class Runner
|
|
10
|
+
using CoreExtensions
|
|
11
|
+
|
|
12
|
+
class << self
|
|
13
|
+
def create(count)
|
|
14
|
+
# We are unable to load parallel tests' tasks in the normal way (top of file)
|
|
15
|
+
# because it requires that the Rails.application instance already be configured
|
|
16
|
+
require "parallel_tests/tasks"
|
|
17
|
+
|
|
18
|
+
ENV["PARALLEL_TEST_FIRST_IS_1"] = "true"
|
|
19
|
+
command = ["bundle", "exec", "rake", "db:create", "RAILS_ENV=#{ParallelTests::Tasks.rails_env}"]
|
|
20
|
+
args = {count: count.to_s}
|
|
21
|
+
ParallelTests::Tasks.run_in_parallel(command, args)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def run(opts = {})
|
|
25
|
+
files = opts[:files]
|
|
26
|
+
formatters = opts[:formatters]
|
|
27
|
+
tags = opts[:tags]
|
|
28
|
+
parallel_options = opts[:parallel_options] || {}
|
|
29
|
+
|
|
30
|
+
start_time = opts.fetch(:start_time) { RSpec::Core::Time.now }
|
|
31
|
+
runtime_log = opts.fetch(:runtime_log, nil)
|
|
32
|
+
verbose = opts.fetch(:verbose, false)
|
|
33
|
+
fail_fast = opts.fetch(:fail_fast, nil)
|
|
34
|
+
count = opts.fetch(:count, nil)
|
|
35
|
+
seed = opts.fetch(:seed, nil)
|
|
36
|
+
seed_used = !seed.nil?
|
|
37
|
+
print_failed_group = opts.fetch(:print_failed_group, false)
|
|
38
|
+
nice = opts.fetch(:nice, false)
|
|
39
|
+
|
|
40
|
+
use_runtime_info = files == ["spec"]
|
|
41
|
+
|
|
42
|
+
if use_runtime_info
|
|
43
|
+
parallel_options[:runtime_log] = runtime_log
|
|
44
|
+
else
|
|
45
|
+
parallel_options[:group_by] = :filesize
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
warn("VERBOSE") if verbose
|
|
49
|
+
|
|
50
|
+
reporter = Reporter.from_config(formatters, start_time, seed, seed_used, files, parallel_options)
|
|
51
|
+
|
|
52
|
+
new(
|
|
53
|
+
reporter: reporter,
|
|
54
|
+
formatters: formatters,
|
|
55
|
+
start_time: start_time,
|
|
56
|
+
files: files,
|
|
57
|
+
tags: tags,
|
|
58
|
+
runtime_log: runtime_log,
|
|
59
|
+
verbose: verbose,
|
|
60
|
+
fail_fast: fail_fast,
|
|
61
|
+
count: count,
|
|
62
|
+
seed: seed,
|
|
63
|
+
seed_used: seed_used,
|
|
64
|
+
print_failed_group: print_failed_group,
|
|
65
|
+
use_runtime_info: use_runtime_info,
|
|
66
|
+
parallel_options: parallel_options,
|
|
67
|
+
nice: nice,
|
|
68
|
+
).run
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def initialize(**opts)
|
|
73
|
+
@formatters = opts[:formatters]
|
|
74
|
+
@reporter = opts[:reporter]
|
|
75
|
+
@files = opts[:files]
|
|
76
|
+
@tags = opts[:tags]
|
|
77
|
+
@verbose = opts[:verbose]
|
|
78
|
+
@fail_fast = opts[:fail_fast]
|
|
79
|
+
@start_time = opts[:start_time]
|
|
80
|
+
@count = opts[:count]
|
|
81
|
+
@seed = opts[:seed]
|
|
82
|
+
@seed_used = opts[:seed_used]
|
|
83
|
+
@nice = opts[:nice]
|
|
84
|
+
@use_runtime_info = opts[:use_runtime_info]
|
|
85
|
+
|
|
86
|
+
@load_time = 0
|
|
87
|
+
@load_count = 0
|
|
88
|
+
@failure_count = 0
|
|
89
|
+
|
|
90
|
+
# Supports runtime_log as a top level option,
|
|
91
|
+
# but also nested inside parallel_options
|
|
92
|
+
@runtime_log = opts[:runtime_log] || "tmp/turbo_rspec_runtime.log"
|
|
93
|
+
@parallel_options = opts.fetch(:parallel_options, {})
|
|
94
|
+
@parallel_options[:runtime_log] ||= @runtime_log
|
|
95
|
+
@record_runtime = @parallel_options[:group_by] == :runtime
|
|
96
|
+
|
|
97
|
+
@messages = Thread::Queue.new
|
|
98
|
+
@threads = []
|
|
99
|
+
@wait_threads = []
|
|
100
|
+
@error = false
|
|
101
|
+
@print_failed_group = opts[:print_failed_group]
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def run
|
|
105
|
+
@num_processes = [
|
|
106
|
+
ParallelTests.determine_number_of_processes(@count),
|
|
107
|
+
ParallelTests::RSpec::Runner.tests_with_size(@files, {}).size,
|
|
108
|
+
].min
|
|
109
|
+
|
|
110
|
+
tests_in_groups =
|
|
111
|
+
ParallelTests::RSpec::Runner.tests_in_groups(
|
|
112
|
+
@files,
|
|
113
|
+
@num_processes,
|
|
114
|
+
**@parallel_options,
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
subprocess_opts = {
|
|
118
|
+
record_runtime: @record_runtime,
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
@reporter.report(tests_in_groups) do |_reporter|
|
|
122
|
+
old_signal = Signal.trap(:INT) { handle_interrupt }
|
|
123
|
+
|
|
124
|
+
@wait_threads = tests_in_groups.map.with_index do |tests, process_id|
|
|
125
|
+
start_regular_subprocess(tests, process_id + 1, **subprocess_opts)
|
|
126
|
+
end.compact
|
|
127
|
+
@interrupt_handled = false
|
|
128
|
+
|
|
129
|
+
handle_messages
|
|
130
|
+
|
|
131
|
+
@threads.each(&:join)
|
|
132
|
+
|
|
133
|
+
report_failed_group(tests_in_groups) if @print_failed_group
|
|
134
|
+
|
|
135
|
+
Signal.trap(:INT, old_signal)
|
|
136
|
+
|
|
137
|
+
if @reporter.failed_examples.empty? && @wait_threads.map(&:value).all?(&:success?)
|
|
138
|
+
0
|
|
139
|
+
else
|
|
140
|
+
# From https://github.com/galtzo-floss/turbo_tests2/pull/20/
|
|
141
|
+
@wait_threads.map { |thread| thread.value.exitstatus }.max
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
private
|
|
147
|
+
|
|
148
|
+
def handle_interrupt
|
|
149
|
+
if @interrupt_handled
|
|
150
|
+
Kernel.exit
|
|
151
|
+
else
|
|
152
|
+
puts "\nShutting down subprocesses..."
|
|
153
|
+
@wait_threads.each do |wait_thr|
|
|
154
|
+
begin
|
|
155
|
+
child_pid = wait_thr.pid
|
|
156
|
+
pgid = Process.respond_to?(:getpgid) ? Process.getpgid(child_pid) : 0
|
|
157
|
+
Process.kill(:INT, child_pid) if Process.pid != pgid
|
|
158
|
+
rescue Errno::ESRCH, Errno::ENOENT
|
|
159
|
+
# process already gone — ignore
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
@interrupt_handled = true
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
def start_regular_subprocess(tests, process_id, **opts)
|
|
167
|
+
start_subprocess(
|
|
168
|
+
{"TEST_ENV_NUMBER" => process_id.to_s},
|
|
169
|
+
@tags.map { |tag| "--tag=#{tag}" },
|
|
170
|
+
tests,
|
|
171
|
+
process_id,
|
|
172
|
+
**opts,
|
|
173
|
+
)
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
def start_subprocess(env, extra_args, tests, process_id, record_runtime:)
|
|
177
|
+
if tests.empty?
|
|
178
|
+
@messages << {
|
|
179
|
+
type: "exit",
|
|
180
|
+
process_id: process_id,
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
nil
|
|
184
|
+
else
|
|
185
|
+
env["RSPEC_FORMATTER_OUTPUT_ID"] = SecureRandom.uuid
|
|
186
|
+
env["RUBYOPT"] = ["-I#{File.expand_path("..", __dir__)}", ENV["RUBYOPT"]].compact.join(" ")
|
|
187
|
+
env["RSPEC_SILENCE_FILTER_ANNOUNCEMENTS"] = "1"
|
|
188
|
+
|
|
189
|
+
command_name =
|
|
190
|
+
if ENV["RSPEC_EXECUTABLE"]
|
|
191
|
+
ENV["RSPEC_EXECUTABLE"].split
|
|
192
|
+
elsif ENV["BUNDLE_BIN_PATH"]
|
|
193
|
+
[ENV["BUNDLE_BIN_PATH"], "exec", "rspec"]
|
|
194
|
+
else
|
|
195
|
+
"rspec"
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
record_runtime_options =
|
|
199
|
+
if record_runtime
|
|
200
|
+
[
|
|
201
|
+
"--format",
|
|
202
|
+
"ParallelTests::RSpec::RuntimeLogger",
|
|
203
|
+
"--out",
|
|
204
|
+
@runtime_log,
|
|
205
|
+
]
|
|
206
|
+
else
|
|
207
|
+
[]
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
seed_option = if @seed_used
|
|
211
|
+
[
|
|
212
|
+
"--seed", @seed,
|
|
213
|
+
]
|
|
214
|
+
else
|
|
215
|
+
[]
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
spec_opts = ParallelTests::RSpec::Runner.send(:spec_opts)
|
|
219
|
+
|
|
220
|
+
command = [
|
|
221
|
+
*command_name,
|
|
222
|
+
*extra_args,
|
|
223
|
+
*seed_option,
|
|
224
|
+
"--format",
|
|
225
|
+
"TurboTests::JsonRowsFormatter",
|
|
226
|
+
*record_runtime_options,
|
|
227
|
+
*spec_opts,
|
|
228
|
+
*tests,
|
|
229
|
+
]
|
|
230
|
+
command.unshift("nice") if @nice
|
|
231
|
+
|
|
232
|
+
if @verbose
|
|
233
|
+
command_str = [
|
|
234
|
+
env.map { |k, v| "#{k}=#{v}" }.join(" "),
|
|
235
|
+
command.join(" "),
|
|
236
|
+
].select { |x| x.size > 0 }.join(" ")
|
|
237
|
+
|
|
238
|
+
warn("Process #{process_id}: #{command_str}")
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
stdin, stdout, stderr, wait_thr = Open3.popen3(env, *command)
|
|
242
|
+
stdin.close
|
|
243
|
+
|
|
244
|
+
# rubocop:disable ThreadSafety/NewThread
|
|
245
|
+
@threads <<
|
|
246
|
+
Thread.new do
|
|
247
|
+
stdout.each_line do |line|
|
|
248
|
+
result = line.split(env["RSPEC_FORMATTER_OUTPUT_ID"])
|
|
249
|
+
|
|
250
|
+
initial = result.shift
|
|
251
|
+
print(initial) unless initial.empty?
|
|
252
|
+
|
|
253
|
+
message = result.shift
|
|
254
|
+
next unless message
|
|
255
|
+
|
|
256
|
+
message = JSON.parse(message, symbolize_names: true)
|
|
257
|
+
|
|
258
|
+
message[:process_id] = process_id
|
|
259
|
+
@messages << message
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
@messages << {type: "exit", process_id: process_id}
|
|
263
|
+
end
|
|
264
|
+
# rubocop:enable ThreadSafety/NewThread
|
|
265
|
+
|
|
266
|
+
@threads << start_copy_thread(stderr, $stderr)
|
|
267
|
+
|
|
268
|
+
# rubocop:disable ThreadSafety/NewThread
|
|
269
|
+
@threads << Thread.new do
|
|
270
|
+
@messages << {type: "error"} unless wait_thr.value.success?
|
|
271
|
+
end
|
|
272
|
+
# rubocop:enable ThreadSafety/NewThread
|
|
273
|
+
|
|
274
|
+
wait_thr
|
|
275
|
+
end
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
def start_copy_thread(src, dst)
|
|
279
|
+
# rubocop:disable ThreadSafety/NewThread
|
|
280
|
+
Thread.new do
|
|
281
|
+
# rubocop:enable ThreadSafety/NewThread
|
|
282
|
+
loop do
|
|
283
|
+
begin
|
|
284
|
+
msg = src.readpartial(4096)
|
|
285
|
+
rescue EOFError
|
|
286
|
+
src.close
|
|
287
|
+
break
|
|
288
|
+
else
|
|
289
|
+
dst.write(msg)
|
|
290
|
+
end
|
|
291
|
+
end
|
|
292
|
+
end
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
def handle_messages
|
|
296
|
+
exited = 0
|
|
297
|
+
|
|
298
|
+
loop do
|
|
299
|
+
message = @messages.pop
|
|
300
|
+
case message[:type]
|
|
301
|
+
when "example_passed"
|
|
302
|
+
example = FakeExample.from_obj(message[:example])
|
|
303
|
+
@reporter.example_passed(example)
|
|
304
|
+
when "group_started"
|
|
305
|
+
@reporter.group_started(message[:group].to_struct)
|
|
306
|
+
when "group_finished"
|
|
307
|
+
@reporter.group_finished
|
|
308
|
+
when "example_pending"
|
|
309
|
+
example = FakeExample.from_obj(message[:example])
|
|
310
|
+
@reporter.example_pending(example)
|
|
311
|
+
when "load_summary"
|
|
312
|
+
message = message[:summary]
|
|
313
|
+
# NOTE: notifications order and content is not guaranteed hence the fetch
|
|
314
|
+
# and count increment tracking to get the latest accumulated load time
|
|
315
|
+
@reporter.load_time = message[:load_time] if message.fetch(:count, 0) > @load_count
|
|
316
|
+
when "example_failed"
|
|
317
|
+
example = FakeExample.from_obj(message[:example])
|
|
318
|
+
@reporter.example_failed(example)
|
|
319
|
+
@failure_count += 1
|
|
320
|
+
if fail_fast_met
|
|
321
|
+
@threads.each(&:kill)
|
|
322
|
+
break
|
|
323
|
+
end
|
|
324
|
+
when "message"
|
|
325
|
+
if message[:message].include?("An error occurred") || message[:message].include?("occurred outside of examples")
|
|
326
|
+
@reporter.error_outside_of_examples(message[:message])
|
|
327
|
+
@error = true
|
|
328
|
+
else
|
|
329
|
+
@reporter.message(message[:message])
|
|
330
|
+
end
|
|
331
|
+
when "seed"
|
|
332
|
+
when "close"
|
|
333
|
+
when "error"
|
|
334
|
+
# Do nothing
|
|
335
|
+
nil
|
|
336
|
+
when "exit"
|
|
337
|
+
exited += 1
|
|
338
|
+
break if exited == @num_processes
|
|
339
|
+
else
|
|
340
|
+
warn("Unhandled message in main process: #{message}")
|
|
341
|
+
end
|
|
342
|
+
|
|
343
|
+
$stdout.flush
|
|
344
|
+
end
|
|
345
|
+
rescue Interrupt
|
|
346
|
+
end
|
|
347
|
+
|
|
348
|
+
def fail_fast_met
|
|
349
|
+
!@fail_fast.nil? && @failure_count >= @fail_fast
|
|
350
|
+
end
|
|
351
|
+
|
|
352
|
+
def report_failed_group(tests_in_groups)
|
|
353
|
+
@wait_threads.map(&:value).each_with_index do |value, index|
|
|
354
|
+
next if value.success?
|
|
355
|
+
|
|
356
|
+
failing_group = tests_in_groups[index].join(" ")
|
|
357
|
+
puts "Group that failed: #{failing_group}"
|
|
358
|
+
end
|
|
359
|
+
end
|
|
360
|
+
end
|
|
361
|
+
end
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module TurboTests
|
|
4
|
+
class Shim
|
|
5
|
+
Result = Struct.new(:status, :path, :message, :exit_code, keyword_init: true)
|
|
6
|
+
|
|
7
|
+
DEFAULT_RELATIVE_PATH = File.join("bin", "turbo_tests")
|
|
8
|
+
MANAGED_MARKER = "Generated by turbo_tests2 shim install"
|
|
9
|
+
|
|
10
|
+
class << self
|
|
11
|
+
def install(project_root: Dir.pwd, path: nil)
|
|
12
|
+
new(project_root: project_root, path: path).install
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def remove(project_root: Dir.pwd, path: nil)
|
|
16
|
+
new(project_root: project_root, path: path).remove
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
attr_reader :project_root, :path
|
|
21
|
+
|
|
22
|
+
def initialize(project_root: Dir.pwd, path: nil)
|
|
23
|
+
@project_root = File.expand_path(project_root)
|
|
24
|
+
@path = File.expand_path(path || DEFAULT_RELATIVE_PATH, @project_root)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def install
|
|
28
|
+
existing_managed = false
|
|
29
|
+
if File.exist?(path)
|
|
30
|
+
existing = File.read(path)
|
|
31
|
+
return result(:unchanged, "Shim already installed at #{display_path}.", 0) if existing == rendered_content
|
|
32
|
+
return result(:conflict, "Refusing to overwrite unmanaged file at #{display_path}.", 1) unless managed_content?(existing)
|
|
33
|
+
existing_managed = true
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
FileUtils.mkdir_p(File.dirname(path))
|
|
37
|
+
File.write(path, rendered_content)
|
|
38
|
+
FileUtils.chmod(0o755, path)
|
|
39
|
+
|
|
40
|
+
result(existing_managed ? :updated : :installed, "Installed turbo_tests shim at #{display_path}.", 0)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def remove
|
|
44
|
+
return result(:missing, "No shim found at #{display_path}.", 0) unless File.exist?(path)
|
|
45
|
+
|
|
46
|
+
content = File.read(path)
|
|
47
|
+
return result(:conflict, "Refusing to remove unmanaged file at #{display_path}.", 1) unless managed_content?(content)
|
|
48
|
+
|
|
49
|
+
File.delete(path)
|
|
50
|
+
result(:removed, "Removed turbo_tests shim at #{display_path}.", 0)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
private
|
|
54
|
+
|
|
55
|
+
def display_path
|
|
56
|
+
path.sub(%r{\A#{Regexp.escape(project_root)}/?}, "")
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def managed_content?(content)
|
|
60
|
+
content.include?(MANAGED_MARKER)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def rendered_content
|
|
64
|
+
<<~SH
|
|
65
|
+
#!/usr/bin/env sh
|
|
66
|
+
# #{MANAGED_MARKER}
|
|
67
|
+
exec bundle exec turbo_tests2 "$@"
|
|
68
|
+
SH
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def result(status, message, exit_code)
|
|
72
|
+
Result.new(status: status, path: path, message: message, exit_code: exit_code)
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
data/lib/turbo_tests.rb
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "version_gem"
|
|
4
|
+
require_relative "turbo_tests/version"
|
|
5
|
+
|
|
6
|
+
TurboTests::Version.class_eval do
|
|
7
|
+
extend VersionGem::Basic
|
|
8
|
+
end
|
|
9
|
+
require "securerandom"
|
|
10
|
+
require "open3"
|
|
11
|
+
require "fileutils"
|
|
12
|
+
require "json"
|
|
13
|
+
|
|
14
|
+
require "rspec"
|
|
15
|
+
|
|
16
|
+
require "parallel_tests"
|
|
17
|
+
require "parallel_tests/rspec/runner"
|
|
18
|
+
|
|
19
|
+
require "turbo_tests/reporter"
|
|
20
|
+
require "turbo_tests/runner"
|
|
21
|
+
require "turbo_tests/json_rows_formatter"
|
|
22
|
+
|
|
23
|
+
module TurboTests
|
|
24
|
+
autoload :CLI, "turbo_tests/cli"
|
|
25
|
+
autoload :Shim, "turbo_tests/shim"
|
|
26
|
+
FakeException = Struct.new(:backtrace, :message, :cause)
|
|
27
|
+
class FakeException
|
|
28
|
+
class << self
|
|
29
|
+
def from_obj(obj)
|
|
30
|
+
return unless obj
|
|
31
|
+
|
|
32
|
+
klass =
|
|
33
|
+
Class.new(FakeException) do
|
|
34
|
+
define_singleton_method(:name) do
|
|
35
|
+
obj[:class_name]
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
klass.new(
|
|
40
|
+
obj[:backtrace],
|
|
41
|
+
obj[:message],
|
|
42
|
+
FakeException.from_obj(obj[:cause]),
|
|
43
|
+
)
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
FakeExecutionResult = Struct.new(
|
|
49
|
+
:example_skipped?,
|
|
50
|
+
:pending_message,
|
|
51
|
+
:status,
|
|
52
|
+
:pending_fixed?,
|
|
53
|
+
:exception,
|
|
54
|
+
:pending_exception,
|
|
55
|
+
)
|
|
56
|
+
class FakeExecutionResult
|
|
57
|
+
class << self
|
|
58
|
+
def from_obj(obj)
|
|
59
|
+
new(
|
|
60
|
+
obj[:example_skipped?],
|
|
61
|
+
obj[:pending_message],
|
|
62
|
+
obj[:status].to_sym,
|
|
63
|
+
obj[:pending_fixed?],
|
|
64
|
+
FakeException.from_obj(obj[:exception]),
|
|
65
|
+
FakeException.from_obj(obj[:exception]),
|
|
66
|
+
)
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
FakeExample = Struct.new(
|
|
72
|
+
:execution_result,
|
|
73
|
+
:location,
|
|
74
|
+
:description,
|
|
75
|
+
:full_description,
|
|
76
|
+
:metadata,
|
|
77
|
+
:location_rerun_argument,
|
|
78
|
+
)
|
|
79
|
+
class FakeExample
|
|
80
|
+
class << self
|
|
81
|
+
def from_obj(obj)
|
|
82
|
+
metadata = obj[:metadata]
|
|
83
|
+
|
|
84
|
+
metadata[:shared_group_inclusion_backtrace].map! do |frame|
|
|
85
|
+
RSpec::Core::SharedExampleGroupInclusionStackFrame.new(
|
|
86
|
+
frame[:shared_group_name],
|
|
87
|
+
frame[:inclusion_location],
|
|
88
|
+
)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
metadata[:shared_group_inclusion_backtrace] = metadata.delete(:shared_group_inclusion_backtrace)
|
|
92
|
+
|
|
93
|
+
new(
|
|
94
|
+
FakeExecutionResult.from_obj(obj[:execution_result]),
|
|
95
|
+
obj[:location],
|
|
96
|
+
obj[:description],
|
|
97
|
+
obj[:full_description],
|
|
98
|
+
metadata,
|
|
99
|
+
obj[:location_rerun_argument],
|
|
100
|
+
)
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def notification
|
|
105
|
+
RSpec::Core::Notifications::ExampleNotification.for(
|
|
106
|
+
self,
|
|
107
|
+
)
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "bundler"
|
|
4
|
+
require "rspec/core"
|
|
5
|
+
|
|
6
|
+
RSpec.shared_context("with simplecov spawn coverage") do
|
|
7
|
+
let(:simplecov_spawn_path) do
|
|
8
|
+
File.expand_path(".simplecov_spawn.rb", Bundler.root.to_s)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
around do |example|
|
|
12
|
+
original_rubyopt = ENV.fetch("RUBYOPT", nil)
|
|
13
|
+
begin
|
|
14
|
+
if defined?(SimpleCov) && SimpleCov.running
|
|
15
|
+
spawn_path = simplecov_spawn_path
|
|
16
|
+
raise ArgumentError, "Expected SimpleCov spawn shim at #{spawn_path}" unless File.file?(spawn_path)
|
|
17
|
+
|
|
18
|
+
ENV["RUBYOPT"] = ["-r#{spawn_path}", original_rubyopt].compact.join(" ").strip
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
example.run
|
|
22
|
+
ensure
|
|
23
|
+
ENV["RUBYOPT"] = original_rubyopt
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
data/lib/turbo_tests2.rb
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
# For technical reasons, if we move to Zeitwerk, this cannot be require_relative.
|
|
2
|
+
# See: https://github.com/fxn/zeitwerk#for_gem_extension
|
|
3
|
+
# But we will use require_relative to avoid the risk of it loading the old turbo_tests gem.
|
|
4
|
+
# Bottom-line: Do not use Zeitwerk in this gem.
|
|
5
|
+
# Hook for other libraries to load this library (e.g. via bundler)
|
|
6
|
+
require_relative "turbo_tests"
|
data.tar.gz.sig
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Yv)� w?Z�Ď_G�]��z�J�����
|