Class: Henitai::Integration::Rspec
- Inherits:
-
Base
- Object
- Base
- Henitai::Integration::Rspec
show all
- Defined in:
- lib/henitai/integration.rb
Overview
RSpec integration adapter.
Constant Summary
collapse
- DEFAULT_SUITE_TIMEOUT =
300.0
- REQUIRE_DIRECTIVE_PATTERN =
/
\A\s*
(require|require_relative)
\s*
(?:\(\s*)?
["']([^"']+)["']
\s*\)?
/x
Instance Method Summary
collapse
-
#build_result(wait_result, log_paths) ⇒ Object
-
#combined_log(stdout, stderr) ⇒ Object
-
#excluded_spec_files ⇒ Object
-
#expand_candidates(base_path, required_path) ⇒ Object
-
#fallback_spec_files(subject) ⇒ Object
-
#mutant_log_name(mutant) ⇒ Object
-
#per_test_coverage_supported? ⇒ Boolean
-
#read_log_file(path) ⇒ Object
-
#relative_candidates(spec_file, required_path) ⇒ Object
-
#require_candidates(spec_file, required_path) ⇒ Object
-
#required_files(spec_file) ⇒ Object
-
#requires_source_file?(spec_file, source_file) ⇒ Boolean
-
#requires_source_file_transitively?(spec_file, source_file, visited = []) ⇒ Boolean
-
#resolve_required_file(spec_file, method_name, required_path) ⇒ Object
-
#rspec_config_lines ⇒ Object
-
#rspec_config_path ⇒ Object
-
#rspec_exclude_patterns ⇒ Object
-
#rspec_suite_runner_script ⇒ Object
-
#run_in_child(mutant:, test_files:, log_paths:) ⇒ Object
-
#run_mutant(mutant:, test_files:, timeout:) ⇒ Object
-
#run_suite(test_files, timeout: DEFAULT_SUITE_TIMEOUT) ⇒ Object
-
#scenario_log_paths(name) ⇒ Object
-
#select_tests(subject) ⇒ Object
-
#selection_patterns(subject) ⇒ Object
-
#spawn_mutant(mutant:, test_files:) ⇒ Object
-
#spawn_suite_process(test_files, log_paths) ⇒ Object
-
#spec_files ⇒ Object
-
#suite_command(test_files) ⇒ Object
-
#test_files ⇒ Object
-
#write_combined_log(path, stdout, stderr) ⇒ Object
Methods inherited from Base
#cleanup_process_group, #reap_child, #wait_with_timeout
Instance Method Details
#build_result(wait_result, log_paths) ⇒ Object
596
597
598
599
600
601
602
603
604
605
606
607
|
# File 'lib/henitai/integration.rb', line 596
def build_result(wait_result, log_paths)
stdout = read_log_file(log_paths[:stdout_path])
stderr = read_log_file(log_paths[:stderr_path])
write_combined_log(log_paths[:log_path], stdout, stderr)
ScenarioExecutionResult.build(
wait_result:,
stdout:,
stderr:,
log_path: log_paths[:log_path]
)
end
|
#combined_log(stdout, stderr) ⇒ Object
751
752
753
754
755
756
|
# File 'lib/henitai/integration.rb', line 751
def combined_log(stdout, stderr)
[
(stdout.empty? ? nil : "stdout:\n#{stdout}"),
(stderr.empty? ? nil : "stderr:\n#{stderr}")
].compact.join("\n")
end
|
#excluded_spec_files ⇒ Object
628
629
630
|
# File 'lib/henitai/integration.rb', line 628
def excluded_spec_files
@excluded_spec_files ||= rspec_exclude_patterns.flat_map { |pattern| Dir.glob(pattern) }.uniq
end
|
#expand_candidates(base_path, required_path) ⇒ Object
703
704
705
706
707
708
|
# File 'lib/henitai/integration.rb', line 703
def expand_candidates(base_path, required_path)
[
File.expand_path(required_path, base_path),
File.expand_path("#{required_path}.rb", base_path)
].uniq
end
|
#fallback_spec_files(subject) ⇒ Object
616
617
618
619
620
621
622
623
624
625
626
|
# File 'lib/henitai/integration.rb', line 616
def fallback_spec_files(subject)
return [] unless subject.source_file
matches = spec_files.select do |path|
requires_source_file_transitively?(path, subject.source_file)
rescue StandardError
false
end
matches.empty? ? spec_files : matches
end
|
#mutant_log_name(mutant) ⇒ Object
736
737
738
|
# File 'lib/henitai/integration.rb', line 736
def mutant_log_name(mutant)
"mutant-#{mutant.id}"
end
|
#per_test_coverage_supported? ⇒ Boolean
548
549
550
|
# File 'lib/henitai/integration.rb', line 548
def per_test_coverage_supported?
true
end
|
#read_log_file(path) ⇒ Object
740
741
742
743
744
|
# File 'lib/henitai/integration.rb', line 740
def read_log_file(path)
return "" unless File.exist?(path)
File.read(path)
end
|
#relative_candidates(spec_file, required_path) ⇒ Object
693
694
695
|
# File 'lib/henitai/integration.rb', line 693
def relative_candidates(spec_file, required_path)
expand_candidates(File.dirname(spec_file), required_path)
end
|
#require_candidates(spec_file, required_path) ⇒ Object
697
698
699
700
701
|
# File 'lib/henitai/integration.rb', line 697
def require_candidates(spec_file, required_path)
([File.dirname(spec_file), Dir.pwd] + $LOAD_PATH).flat_map do |base_path|
expand_candidates(base_path, required_path)
end
end
|
#required_files(spec_file) ⇒ Object
673
674
675
676
677
678
679
680
|
# File 'lib/henitai/integration.rb', line 673
def required_files(spec_file)
File.read(spec_file).lines.filter_map do |line|
match = line.match(REQUIRE_DIRECTIVE_PATTERN)
next unless match
resolve_required_file(spec_file, match[1].to_s, match[2].to_s)
end
end
|
#requires_source_file?(spec_file, source_file) ⇒ Boolean
655
656
657
658
659
|
# File 'lib/henitai/integration.rb', line 655
def requires_source_file?(spec_file, source_file)
content = File.read(spec_file)
basename = File.basename(source_file, ".rb")
content.include?(basename) || content.include?(source_file)
end
|
#requires_source_file_transitively?(spec_file, source_file, visited = []) ⇒ Boolean
661
662
663
664
665
666
667
668
669
670
671
|
# File 'lib/henitai/integration.rb', line 661
def requires_source_file_transitively?(spec_file, source_file, visited = [])
normalized_spec_file = File.expand_path(spec_file)
return false if visited.include?(normalized_spec_file)
visited << normalized_spec_file
return true if requires_source_file?(spec_file, source_file)
required_files(spec_file).any? do |required_file|
requires_source_file_transitively?(required_file, source_file, visited)
end
end
|
#resolve_required_file(spec_file, method_name, required_path) ⇒ Object
682
683
684
685
686
687
688
689
690
691
|
# File 'lib/henitai/integration.rb', line 682
def resolve_required_file(spec_file, method_name, required_path)
candidates =
if method_name == "require_relative"
relative_candidates(spec_file, required_path)
else
require_candidates(spec_file, required_path)
end
candidates.find { |candidate| File.file?(candidate) }
end
|
#rspec_config_lines ⇒ Object
638
639
640
641
642
|
# File 'lib/henitai/integration.rb', line 638
def rspec_config_lines
return [] unless File.exist?(rspec_config_path)
File.readlines(rspec_config_path, chomp: true).map(&:strip)
end
|
#rspec_config_path ⇒ Object
644
645
646
|
# File 'lib/henitai/integration.rb', line 644
def rspec_config_path
".rspec"
end
|
#rspec_exclude_patterns ⇒ Object
632
633
634
635
636
|
# File 'lib/henitai/integration.rb', line 632
def rspec_exclude_patterns
rspec_config_lines.filter_map do |line|
line[/\A--exclude-pattern\s+(.+)\z/, 1]
end
end
|
#rspec_suite_runner_script ⇒ Object
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
|
# File 'lib/henitai/integration.rb', line 565
def rspec_suite_runner_script
<<~RUBY
require "rspec/core"
test_files = ARGV.map { |file| File.expand_path(file) }
config = RSpec.configuration
options = RSpec::Core::ConfigurationOptions.new(
["--format", "progress", "--format", "Henitai::CoverageFormatter"]
)
runner = RSpec::Core::Runner.send(:new, options)
RSpec::Core::Runner.send(:trap_interrupt)
runner.send(:configure, $stderr, $stdout)
config.files_to_run = test_files
config.load_spec_files
status = runner.send(:run_specs, RSpec.world.ordered_example_groups)
exit(status.is_a?(Integer) ? status : (status == true ? 0 : 1))
RUBY
end
|
#run_in_child(mutant:, test_files:, log_paths:) ⇒ Object
724
725
726
727
728
729
730
731
732
733
734
|
# File 'lib/henitai/integration.rb', line 724
def run_in_child(mutant:, test_files:, log_paths:)
Thread.report_on_exception = false
with_subprocess_env do
suppress_simplecov!
suppress_coverage!
install_debug_timeout_trap if debug_child?
with_non_interactive_stdin do
run_child_activation_and_tests(mutant:, test_files:, log_paths:)
end
end
end
|
#run_mutant(mutant:, test_files:, timeout:) ⇒ Object
544
545
546
|
# File 'lib/henitai/integration.rb', line 544
def run_mutant(mutant:, test_files:, timeout:)
RspecProcessRunner.new.run_mutant(self, mutant:, test_files:, timeout:)
end
|
#run_suite(test_files, timeout: DEFAULT_SUITE_TIMEOUT) ⇒ Object
#scenario_log_paths(name) ⇒ Object
586
587
588
589
590
591
592
593
594
|
# File 'lib/henitai/integration.rb', line 586
def scenario_log_paths(name)
reports_dir = ENV.fetch("HENITAI_REPORTS_DIR", "reports")
log_dir = File.join(reports_dir, "mutation-logs")
{
stdout_path: File.join(log_dir, "#{name}.stdout.log"),
stderr_path: File.join(log_dir, "#{name}.stderr.log"),
log_path: File.join(log_dir, "#{name}.log")
}
end
|
#select_tests(subject) ⇒ Object
524
525
526
527
528
529
530
531
532
533
534
535
|
# File 'lib/henitai/integration.rb', line 524
def select_tests(subject)
matches = spec_files.select do |path|
content = File.read(path)
selection_patterns(subject).any? { |pattern| content.include?(pattern) }
rescue StandardError
false
end
return matches unless matches.empty?
fallback_spec_files(subject)
end
|
#selection_patterns(subject) ⇒ Object
648
649
650
651
652
653
|
# File 'lib/henitai/integration.rb', line 648
def selection_patterns(subject)
[
subject.expression,
subject.namespace
].compact.uniq.sort_by(&:length).reverse
end
|
#spawn_mutant(mutant:, test_files:) ⇒ Object
539
540
541
542
|
# File 'lib/henitai/integration.rb', line 539
def spawn_mutant(mutant:, test_files:)
log_paths = scenario_log_paths(mutant_log_name(mutant))
RspecProcessRunner.new.spawn_mutant(self, mutant:, test_files:, log_paths:)
end
|
#spawn_suite_process(test_files, log_paths) ⇒ Object
710
711
712
713
714
715
716
717
718
719
720
721
722
|
# File 'lib/henitai/integration.rb', line 710
def spawn_suite_process(test_files, log_paths)
File.open(log_paths[:stdout_path], "w") do |stdout_file|
File.open(log_paths[:stderr_path], "w") do |stderr_file|
Process.spawn(
subprocess_env,
*suite_command(test_files),
out: stdout_file,
err: stderr_file,
pgroup: true
)
end
end
end
|
#spec_files ⇒ Object
609
610
611
612
613
614
|
# File 'lib/henitai/integration.rb', line 609
def spec_files
@spec_files ||= begin
paths = Dir.glob("spec/**/*_spec.rb")
paths - excluded_spec_files
end
end
|
#suite_command(test_files) ⇒ Object
556
557
558
559
560
561
562
563
|
# File 'lib/henitai/integration.rb', line 556
def suite_command(test_files)
[
"bundle", "exec", "ruby",
"-r", "henitai/rspec_coverage_formatter",
"-e", rspec_suite_runner_script,
*test_files
]
end
|
#test_files ⇒ Object
537
|
# File 'lib/henitai/integration.rb', line 537
def test_files = spec_files
|
#write_combined_log(path, stdout, stderr) ⇒ Object
746
747
748
749
|
# File 'lib/henitai/integration.rb', line 746
def write_combined_log(path, stdout, stderr)
FileUtils.mkdir_p(File.dirname(path))
File.write(path, combined_log(stdout, stderr))
end
|