henitai 0.1.10 → 0.2.1

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.
Files changed (60) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +94 -1
  3. data/README.md +33 -7
  4. data/assets/schema/henitai.schema.json +6 -0
  5. data/lib/henitai/cli/clean_command.rb +48 -0
  6. data/lib/henitai/cli/command_support.rb +51 -0
  7. data/lib/henitai/cli/init_command.rb +64 -0
  8. data/lib/henitai/cli/operator_command.rb +95 -0
  9. data/lib/henitai/cli/options.rb +120 -0
  10. data/lib/henitai/cli/run_command.rb +103 -0
  11. data/lib/henitai/cli.rb +17 -327
  12. data/lib/henitai/configuration.rb +26 -12
  13. data/lib/henitai/configuration_validator/rules.rb +143 -0
  14. data/lib/henitai/configuration_validator/scalars.rb +123 -0
  15. data/lib/henitai/configuration_validator.rb +12 -239
  16. data/lib/henitai/coverage_bootstrapper.rb +24 -24
  17. data/lib/henitai/eager_load.rb +36 -5
  18. data/lib/henitai/execution_engine.rb +6 -11
  19. data/lib/henitai/git_diff_analyzer.rb +34 -0
  20. data/lib/henitai/integration/base.rb +171 -0
  21. data/lib/henitai/integration/child_debug_support.rb +115 -0
  22. data/lib/henitai/integration/child_runtime_control.rb +50 -0
  23. data/lib/henitai/integration/coverage_suppression.rb +43 -0
  24. data/lib/henitai/integration/minitest.rb +133 -0
  25. data/lib/henitai/integration/mutant_run_support.rb +77 -0
  26. data/lib/henitai/integration/rspec_child_runner.rb +61 -0
  27. data/lib/henitai/integration/rspec_process_runner.rb +66 -13
  28. data/lib/henitai/integration/rspec_test_selection.rb +135 -0
  29. data/lib/henitai/integration/scenario_log_support.rb +116 -0
  30. data/lib/henitai/integration.rb +43 -519
  31. data/lib/henitai/mutant/activator.rb +13 -79
  32. data/lib/henitai/mutant/parameter_source.rb +98 -0
  33. data/lib/henitai/mutant.rb +14 -2
  34. data/lib/henitai/mutant_generator.rb +21 -2
  35. data/lib/henitai/mutant_history_store/sql.rb +72 -0
  36. data/lib/henitai/mutant_history_store.rb +12 -91
  37. data/lib/henitai/mutant_identity.rb +34 -0
  38. data/lib/henitai/parallel_execution_runner.rb +29 -11
  39. data/lib/henitai/per_test_coverage_collector.rb +3 -1
  40. data/lib/henitai/process_wakeup.rb +49 -0
  41. data/lib/henitai/process_worker_runner.rb +148 -0
  42. data/lib/henitai/reporter.rb +96 -11
  43. data/lib/henitai/result.rb +49 -16
  44. data/lib/henitai/runner.rb +96 -30
  45. data/lib/henitai/scenario_execution_result.rb +16 -3
  46. data/lib/henitai/slot_scheduler/draining.rb +140 -0
  47. data/lib/henitai/slot_scheduler/process_control.rb +43 -0
  48. data/lib/henitai/slot_scheduler.rb +214 -0
  49. data/lib/henitai/static_filter.rb +10 -3
  50. data/lib/henitai/survivor_activation_cache.rb +81 -0
  51. data/lib/henitai/survivor_loader.rb +140 -0
  52. data/lib/henitai/survivor_rerun_strategy.rb +195 -0
  53. data/lib/henitai/survivor_selector.rb +36 -0
  54. data/lib/henitai/survivor_test_filter.rb +72 -0
  55. data/lib/henitai/unparse_helper.rb +5 -2
  56. data/lib/henitai/version.rb +1 -1
  57. data/lib/henitai.rb +10 -0
  58. data/sig/configuration_validator.rbs +46 -22
  59. data/sig/henitai.rbs +329 -53
  60. metadata +46 -2
@@ -1,7 +1,16 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "fileutils"
4
+ require "stringio"
5
+ require_relative "process_wakeup"
4
6
  require_relative "integration/rspec_process_runner"
7
+ require_relative "integration/scenario_log_support"
8
+ require_relative "integration/coverage_suppression"
9
+ require_relative "integration/child_debug_support"
10
+ require_relative "integration/base"
11
+ require_relative "integration/mutant_run_support"
12
+ require_relative "integration/rspec_child_runner"
13
+ require_relative "integration/rspec_test_selection"
5
14
 
6
15
  module Henitai
7
16
  # Namespace for test-framework integrations.
@@ -18,96 +27,6 @@ module Henitai
18
27
  # Built-in integrations:
19
28
  # rspec — RSpec 3.x
20
29
  module Integration
21
- # Shared helpers for capturing stdout/stderr from child test processes.
22
- class ScenarioLogSupport
23
- def capture_child_output(log_paths)
24
- output_files = open_child_output(log_paths)
25
- yield
26
- ensure
27
- close_child_output(output_files)
28
- end
29
-
30
- def with_coverage_dir(mutant_id)
31
- original_coverage_dir = ENV.fetch("HENITAI_COVERAGE_DIR", nil)
32
- ENV["HENITAI_COVERAGE_DIR"] = mutation_coverage_dir(mutant_id)
33
- yield
34
- ensure
35
- if original_coverage_dir.nil?
36
- ENV.delete("HENITAI_COVERAGE_DIR")
37
- else
38
- ENV["HENITAI_COVERAGE_DIR"] = original_coverage_dir
39
- end
40
- end
41
-
42
- def open_child_output(log_paths)
43
- FileUtils.mkdir_p(File.dirname(log_paths[:log_path]))
44
- output_files = build_child_output_files(log_paths)
45
- sync_child_output_files(output_files)
46
- redirect_child_output(output_files)
47
- output_files
48
- end
49
-
50
- def close_child_output(output_files)
51
- return unless output_files
52
-
53
- restore_child_output(output_files)
54
- close_child_output_files(output_files)
55
- end
56
-
57
- def build_child_output_files(log_paths)
58
- {
59
- original_stdout: stdout_stream.dup,
60
- original_stderr: stderr_stream.dup,
61
- stdout_file: File.new(log_paths[:stdout_path], "w"),
62
- stderr_file: File.new(log_paths[:stderr_path], "w")
63
- }
64
- end
65
-
66
- def sync_child_output_files(output_files)
67
- output_files[:stdout_file].sync = true
68
- output_files[:stderr_file].sync = true
69
- end
70
-
71
- def redirect_child_output(output_files)
72
- reopen_child_output_stream(stdout_stream, output_files[:stdout_file])
73
- reopen_child_output_stream(stderr_stream, output_files[:stderr_file])
74
- $stdout = stdout_stream
75
- $stderr = stderr_stream
76
- end
77
-
78
- def restore_child_output(output_files)
79
- reopen_child_output_stream(stdout_stream, output_files[:original_stdout])
80
- reopen_child_output_stream(stderr_stream, output_files[:original_stderr])
81
- $stdout = stdout_stream
82
- $stderr = stderr_stream
83
- end
84
-
85
- def reopen_child_output_stream(stream, original_stream)
86
- stream.reopen(original_stream) if original_stream
87
- end
88
-
89
- def close_child_output_files(output_files)
90
- %i[stdout_file stderr_file original_stdout original_stderr].each do |key|
91
- output_files[key]&.close
92
- end
93
- end
94
-
95
- private
96
-
97
- def mutation_coverage_dir(mutant_id)
98
- reports_dir = ENV.fetch("HENITAI_REPORTS_DIR", "reports")
99
- File.join(reports_dir, "mutation-coverage", mutant_id.to_s)
100
- end
101
-
102
- def stdout_stream
103
- @stdout_stream ||= IO.for_fd(1)
104
- end
105
-
106
- def stderr_stream
107
- @stderr_stream ||= IO.for_fd(2)
108
- end
109
- end
110
-
111
30
  # Integration adapter for RSpec.
112
31
  #
113
32
  # This class exists as the stable public entry point for the RSpec
@@ -125,156 +44,15 @@ module Henitai
125
44
  raise ArgumentError, "Unknown integration: #{name}. Available: #{available}"
126
45
  end
127
46
 
128
- # Base class for all integrations.
129
- class Base
130
- # @param subject [Subject]
131
- # @return [Array<String>] paths to test files that cover this subject
132
- def select_tests(subject)
133
- raise NotImplementedError
134
- end
135
-
136
- # @return [Array<String>] all test files for the configured framework
137
- def test_files
138
- raise NotImplementedError
139
- end
140
-
141
- # Run test files in a child process with the mutant active.
142
- #
143
- # @param mutant [Mutant]
144
- # @param test_files [Array<String>]
145
- # @param timeout [Float] seconds
146
- # @return [ScenarioExecutionResult]
147
- def run_mutant(mutant:, test_files:, timeout:)
148
- raise NotImplementedError
149
- end
150
-
151
- def per_test_coverage_supported?
152
- false
153
- end
154
-
155
- def wait_with_timeout(pid, timeout)
156
- deadline = Process.clock_gettime(Process::CLOCK_MONOTONIC) + timeout
157
-
158
- loop do
159
- wait_result = Process.wait(pid, Process::WNOHANG)
160
- return Process.last_status if wait_result
161
-
162
- if Process.clock_gettime(Process::CLOCK_MONOTONIC) >= deadline
163
- final_wait_result = Process.wait(pid, Process::WNOHANG)
164
- return Process.last_status if final_wait_result
165
-
166
- return handle_timeout(pid)
167
- end
168
-
169
- pause(0.01)
170
- end
171
- end
172
-
173
- def reap_child(pid)
174
- Process.wait(pid)
175
- rescue Errno::ECHILD, Errno::ESRCH
176
- nil
177
- end
178
-
179
- def cleanup_process_group(pid)
180
- Process.kill(:SIGTERM, -pid)
181
- pause(2.0)
182
- Process.kill(:SIGKILL, -pid)
183
- rescue Errno::EPERM
184
- cleanup_child_process(pid)
185
- rescue Errno::ESRCH
186
- nil
187
- end
188
-
189
- private
190
-
191
- def pause(seconds)
192
- sleep(seconds)
193
- end
194
-
195
- def handle_timeout(pid)
196
- begin
197
- cleanup_process_group(pid)
198
- ensure
199
- reap_child(pid)
200
- end
201
- :timeout
202
- end
203
-
204
- def cleanup_child_process(pid)
205
- Process.kill(:SIGTERM, pid)
206
- pause(2.0)
207
- Process.kill(:SIGKILL, pid)
208
- rescue Errno::EPERM, Errno::ESRCH
209
- nil
210
- end
211
-
212
- def rspec_options
213
- []
214
- end
215
-
216
- def subprocess_env
217
- { "PARALLEL_WORKERS" => "1" }
218
- end
219
-
220
- def scenario_log_support
221
- @scenario_log_support ||= ScenarioLogSupport.new
222
- end
223
-
224
- def with_subprocess_env
225
- original_env = {} # : Hash[String, String?]
226
- subprocess_env.each do |key, value|
227
- original_env[key] = ENV.fetch(key, nil)
228
- ENV[key] = value
229
- end
230
- yield
231
- ensure
232
- restore_subprocess_env(original_env)
233
- end
234
-
235
- def restore_subprocess_env(original_env)
236
- original_env.each do |key, value|
237
- if value.nil?
238
- ENV.delete(key)
239
- else
240
- ENV[key] = value
241
- end
242
- end
243
- end
244
- end
245
-
246
47
  # RSpec integration adapter.
247
48
  class Rspec < Base
248
- DEFAULT_SUITE_TIMEOUT = 300.0
249
- REQUIRE_DIRECTIVE_PATTERN = /
250
- \A\s*
251
- (require|require_relative)
252
- \s*
253
- (?:\(\s*)?
254
- ["']([^"']+)["']
255
- \s*\)?
256
- /x
49
+ include MutantRunSupport
50
+ include RspecChildRunner
51
+ include RspecTestSelection
257
52
 
258
- def select_tests(subject)
259
- matches = spec_files.select do |path|
260
- content = File.read(path)
261
- selection_patterns(subject).any? { |pattern| content.include?(pattern) }
262
- rescue StandardError
263
- false
264
- end
265
-
266
- return matches unless matches.empty?
267
-
268
- fallback_spec_files(subject)
269
- end
53
+ DEFAULT_SUITE_TIMEOUT = 300.0
270
54
 
271
- def test_files
272
- spec_files
273
- end
274
-
275
- def run_mutant(mutant:, test_files:, timeout:)
276
- RspecProcessRunner.new.run_mutant(self, mutant:, test_files:, timeout:)
277
- end
55
+ def test_files = spec_files
278
56
 
279
57
  def per_test_coverage_supported?
280
58
  true
@@ -288,140 +66,30 @@ module Henitai
288
66
  [
289
67
  "bundle", "exec", "ruby",
290
68
  "-r", "henitai/rspec_coverage_formatter",
291
- "-S", "rspec", *test_files,
292
- "--format", "progress",
293
- "--format", "Henitai::CoverageFormatter"
69
+ "-e", rspec_suite_runner_script,
70
+ *test_files
294
71
  ]
295
72
  end
296
73
 
297
- def run_tests(test_files)
298
- require "rspec/core"
299
- status = RSpec::Core::Runner.run(test_files + rspec_options)
300
- return status if status.is_a?(Integer)
74
+ def rspec_suite_runner_script
75
+ <<~RUBY
76
+ require "rspec/core"
301
77
 
302
- status == true ? 0 : 1
303
- end
78
+ test_files = ARGV.map { |file| File.expand_path(file) }
79
+ config = RSpec.configuration
80
+ options = RSpec::Core::ConfigurationOptions.new(
81
+ ["--format", "progress", "--format", "Henitai::CoverageFormatter"]
82
+ )
83
+ runner = RSpec::Core::Runner.send(:new, options)
304
84
 
305
- def scenario_log_paths(name)
306
- reports_dir = ENV.fetch("HENITAI_REPORTS_DIR", "reports")
307
- log_dir = File.join(reports_dir, "mutation-logs")
308
- {
309
- stdout_path: File.join(log_dir, "#{name}.stdout.log"),
310
- stderr_path: File.join(log_dir, "#{name}.stderr.log"),
311
- log_path: File.join(log_dir, "#{name}.log")
312
- }
313
- end
314
-
315
- def build_result(wait_result, log_paths)
316
- stdout = read_log_file(log_paths[:stdout_path])
317
- stderr = read_log_file(log_paths[:stderr_path])
318
- write_combined_log(log_paths[:log_path], stdout, stderr)
319
-
320
- ScenarioExecutionResult.build(
321
- wait_result:,
322
- stdout:,
323
- stderr:,
324
- log_path: log_paths[:log_path]
325
- )
326
- end
327
-
328
- def spec_files
329
- paths = Dir.glob("spec/**/*_spec.rb")
330
- paths - excluded_spec_files
331
- end
85
+ RSpec::Core::Runner.send(:trap_interrupt)
86
+ runner.send(:configure, $stderr, $stdout)
87
+ config.files_to_run = test_files
88
+ config.load_spec_files
332
89
 
333
- def fallback_spec_files(subject)
334
- return [] unless subject.source_file
335
-
336
- matches = spec_files.select do |path|
337
- requires_source_file_transitively?(path, subject.source_file)
338
- rescue StandardError
339
- false
340
- end
341
-
342
- matches.empty? ? spec_files : matches
343
- end
344
-
345
- def excluded_spec_files
346
- rspec_exclude_patterns.flat_map { |pattern| Dir.glob(pattern) }.uniq
347
- end
348
-
349
- def rspec_exclude_patterns
350
- rspec_config_lines.filter_map do |line|
351
- line[/\A--exclude-pattern\s+(.+)\z/, 1]
352
- end
353
- end
354
-
355
- def rspec_config_lines
356
- return [] unless File.exist?(rspec_config_path)
357
-
358
- File.readlines(rspec_config_path, chomp: true).map(&:strip)
359
- end
360
-
361
- def rspec_config_path
362
- ".rspec"
363
- end
364
-
365
- def selection_patterns(subject)
366
- [
367
- subject.expression,
368
- subject.namespace
369
- ].compact.uniq.sort_by(&:length).reverse
370
- end
371
-
372
- def requires_source_file?(spec_file, source_file)
373
- content = File.read(spec_file)
374
- basename = File.basename(source_file, ".rb")
375
- content.include?(basename) || content.include?(source_file)
376
- end
377
-
378
- def requires_source_file_transitively?(spec_file, source_file, visited = [])
379
- normalized_spec_file = File.expand_path(spec_file)
380
- return false if visited.include?(normalized_spec_file)
381
-
382
- visited << normalized_spec_file
383
- return true if requires_source_file?(spec_file, source_file)
384
-
385
- required_files(spec_file).any? do |required_file|
386
- requires_source_file_transitively?(required_file, source_file, visited)
387
- end
388
- end
389
-
390
- def required_files(spec_file)
391
- File.read(spec_file).lines.filter_map do |line|
392
- match = line.match(REQUIRE_DIRECTIVE_PATTERN)
393
- next unless match
394
-
395
- resolve_required_file(spec_file, match[1].to_s, match[2].to_s)
396
- end
397
- end
398
-
399
- def resolve_required_file(spec_file, method_name, required_path)
400
- candidates =
401
- if method_name == "require_relative"
402
- relative_candidates(spec_file, required_path)
403
- else
404
- require_candidates(spec_file, required_path)
405
- end
406
-
407
- candidates.find { |candidate| File.file?(candidate) }
408
- end
409
-
410
- def relative_candidates(spec_file, required_path)
411
- expand_candidates(File.dirname(spec_file), required_path)
412
- end
413
-
414
- def require_candidates(spec_file, required_path)
415
- ([File.dirname(spec_file), Dir.pwd] + $LOAD_PATH).flat_map do |base_path|
416
- expand_candidates(base_path, required_path)
417
- end
418
- end
419
-
420
- def expand_candidates(base_path, required_path)
421
- [
422
- File.expand_path(required_path, base_path),
423
- File.expand_path("#{required_path}.rb", base_path)
424
- ].uniq
90
+ status = runner.send(:run_specs, RSpec.world.ordered_example_groups)
91
+ exit(status.is_a?(Integer) ? status : (status == true ? 0 : 1))
92
+ RUBY
425
93
  end
426
94
 
427
95
  def spawn_suite_process(test_files, log_paths)
@@ -438,168 +106,24 @@ module Henitai
438
106
  end
439
107
  end
440
108
 
441
- def run_in_child(mutant:, test_files:, log_paths:)
442
- Thread.report_on_exception = false
443
- with_subprocess_env do
444
- scenario_log_support.with_coverage_dir(mutant.id) do
445
- scenario_log_support.capture_child_output(log_paths) do
446
- return 2 if Mutant::Activator.activate!(mutant) == :compile_error
447
-
448
- run_tests(test_files)
449
- end
450
- end
451
- end
452
- end
453
-
454
- def read_log_file(path)
455
- return "" unless File.exist?(path)
456
-
457
- File.read(path)
458
- end
459
-
460
- def write_combined_log(path, stdout, stderr)
461
- FileUtils.mkdir_p(File.dirname(path))
462
- File.write(path, combined_log(stdout, stderr))
463
- end
464
-
465
- def combined_log(stdout, stderr)
466
- [
467
- (stdout.empty? ? nil : "stdout:\n#{stdout}"),
468
- (stderr.empty? ? nil : "stderr:\n#{stderr}")
469
- ].compact.join("\n")
470
- end
471
- end
472
-
473
- # Stores the child-process log helpers shared by the integration specs.
474
- class ScenarioLogSupport
475
- def read_log_file(path)
476
- return "" unless File.exist?(path)
477
-
478
- File.read(path)
479
- end
480
-
481
- def write_combined_log(path, stdout, stderr)
482
- FileUtils.mkdir_p(File.dirname(path))
483
- File.write(path, combined_log(stdout, stderr))
484
- end
485
-
486
- def combined_log(stdout, stderr)
487
- [
488
- (stdout.empty? ? nil : "stdout:\n#{stdout}"),
489
- (stderr.empty? ? nil : "stderr:\n#{stderr}")
490
- ].compact.join("\n")
491
- end
492
- end
493
-
494
- # Prepended onto SimpleCov's singleton class to turn start into a no-op
495
- # during mutant child runs. Using prepend avoids "method redefined" warnings.
496
- module SimpleCovStartSuppressor
497
- def start(*_args) = nil
498
- end
499
-
500
- # Minitest integration adapter.
501
- #
502
- # Coverage formatter injection remains implemented in the RSpec child
503
- # runner. Minitest shares selection and execution semantics, but per-test
504
- # coverage collection is not yet wired into this path.
505
- class Minitest < Rspec
506
- def per_test_coverage_supported?
507
- true
508
- end
509
-
510
- def run_mutant(mutant:, test_files:, timeout:)
511
- setup_load_path
512
- super
513
- end
514
-
515
- def run_in_child(mutant:, test_files:, log_paths:)
516
- ENV["RAILS_ENV"] = "test" unless ENV["RAILS_ENV"] == "test"
517
- preload_environment
518
- super
519
- end
520
-
521
- def run_suite(test_files, timeout: DEFAULT_SUITE_TIMEOUT)
522
- log_paths = scenario_log_paths("baseline")
523
- pid = spawn_suite_process(test_files, log_paths)
524
- wait_result = wait_with_timeout(pid, timeout)
525
- build_result(wait_result, log_paths)
526
- ensure
527
- cleanup_suite_process(pid, wait_result)
528
- end
529
-
530
109
  private
531
110
 
532
- def suite_command(test_files)
533
- ["bundle", "exec", "ruby", "-I", "test",
534
- "-r", "henitai/minitest_simplecov",
535
- "-r", "henitai/minitest_coverage_hook",
536
- "-e", "ARGV.each { |f| require File.expand_path(f) }",
537
- *test_files]
538
- end
539
-
540
111
  def run_tests(test_files)
541
- suppress_simplecov!
542
- test_files.each { |file| require File.expand_path(file) }
543
- # @type var empty_args: Array[String]
544
- empty_args = []
545
- status = ::Minitest.run(empty_args)
112
+ require "rspec/core"
113
+ ::RSpec.__send__(:configuration).fail_if_no_examples = true
114
+ debug_child_rspec_trace(test_files:, rspec_options: [], rspec_argv: test_files)
115
+ debug_child_example_count("before_run") # steep:ignore Ruby::NoMethod
116
+ debug_child_puts("[henitai-debug-child] runner_run_start")
117
+ status = run_rspec_runner(test_files)
118
+ debug_child_puts("[henitai-debug-child] runner_run_return status=#{status.inspect}")
119
+ debug_child_example_count("after_run") # steep:ignore Ruby::NoMethod
120
+ debug_child_rspec_exit(status)
546
121
  return status if status.is_a?(Integer)
547
122
 
548
123
  status == true ? 0 : 1
549
124
  end
550
-
551
- def preload_environment
552
- env_file = File.expand_path("config/environment.rb")
553
- require env_file if File.exist?(env_file)
554
- end
555
-
556
- def setup_load_path
557
- test_dir = File.expand_path("test")
558
- $LOAD_PATH.unshift(test_dir) unless $LOAD_PATH.include?(test_dir)
559
- end
560
-
561
- def suppress_simplecov!
562
- require "simplecov"
563
- sc = Object.const_get(:SimpleCov) # steep:ignore Ruby::UnknownConstant
564
- return if sc.singleton_class.ancestors.include?(SimpleCovStartSuppressor)
565
-
566
- sc.singleton_class.prepend(SimpleCovStartSuppressor)
567
- rescue LoadError, NameError
568
- nil
569
- end
570
-
571
- def subprocess_env
572
- env = super
573
- env["RAILS_ENV"] = "test" unless ENV["RAILS_ENV"] == "test"
574
- env["PARALLEL_WORKERS"] = "1"
575
- env
576
- end
577
-
578
- def spawn_suite_process(test_files, log_paths)
579
- FileUtils.mkdir_p(File.dirname(log_paths[:stdout_path]))
580
- File.open(log_paths[:stdout_path], "w") do |stdout_file|
581
- File.open(log_paths[:stderr_path], "w") do |stderr_file|
582
- Process.spawn(
583
- subprocess_env,
584
- *suite_command(test_files),
585
- out: stdout_file,
586
- err: stderr_file
587
- )
588
- end
589
- end
590
- end
591
-
592
- def cleanup_suite_process(pid, wait_result)
593
- return unless pid
594
-
595
- cleanup_child_process(pid)
596
- reap_child(pid) if wait_result.nil?
597
- end
598
-
599
- def spec_files
600
- (Dir.glob("test/**/*_test.rb") + Dir.glob("test/**/*_spec.rb"))
601
- .reject { |f| f.start_with?("test/system/") }
602
- end
603
125
  end
604
126
  end
605
127
  end
128
+
129
+ require_relative "integration/minitest"