mutation_tester 1.5.0 → 1.6.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.
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../test_recorder'
4
+
5
+ module MutationTester
6
+ module TestRecorder
7
+ module RSpecHook
8
+ STATUSES = { passed: 'passed', failed: 'failed', pending: 'skipped' }.freeze
9
+
10
+ class << self
11
+ def install
12
+ return if @installed
13
+
14
+ @installed = true
15
+ ::RSpec.configure do |config|
16
+ config.after(:suite) { MutationTester::TestRecorder::RSpecHook.flush }
17
+ end
18
+ end
19
+
20
+ def flush
21
+ return unless TestRecorder.active?
22
+
23
+ TestRecorder.write(all_examples.filter_map { |example| entry_for(example) })
24
+ rescue StandardError => e
25
+ Kernel.warn "[MutationTester] The kill matrix could not record the rspec results: #{e.class}: #{e.message}"
26
+ end
27
+
28
+ private
29
+
30
+ def all_examples
31
+ ::RSpec.world.example_groups.flat_map(&:descendants).flat_map(&:examples)
32
+ end
33
+
34
+ def entry_for(example)
35
+ status = STATUSES[example.execution_result.status]
36
+ return nil unless status
37
+
38
+ metadata = example.metadata
39
+ {
40
+ id: id_for(metadata),
41
+ name: example.full_description,
42
+ line: metadata[:file_path] == rerun_path(metadata) ? metadata[:line_number] : nil,
43
+ status: status
44
+ }
45
+ end
46
+
47
+ def id_for(metadata)
48
+ path = TestRecorder.relative_to_root(File.expand_path(rerun_path(metadata)))
49
+ return "#{path}[#{metadata[:scoped_id]}]" if metadata[:scoped_id]
50
+
51
+ "#{path}:#{metadata[:line_number]}"
52
+ end
53
+
54
+ def rerun_path(metadata)
55
+ metadata[:rerun_file_path] || metadata[:file_path]
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
61
+
62
+ MutationTester::TestRecorder::RSpecHook.install
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+ require 'tempfile'
5
+
6
+ module MutationTester
7
+ module TestRecorder
8
+ LOG_ENV = 'MUTATION_TESTER_TEST_LOG'
9
+ SCOPE_ENV = 'MUTATION_TESTER_TEST_LOG_SCOPE'
10
+ ROOT_ENV = 'MUTATION_TESTER_TEST_LOG_ROOT'
11
+ FAILED = 'failed'
12
+ RSPEC_HOOK_PATH = File.expand_path('test_recorder/rspec_hook.rb', __dir__).freeze
13
+ MINITEST_HOOK_PATH = File.expand_path('test_recorder/minitest_hook.rb', __dir__).freeze
14
+
15
+ class << self
16
+ def capture(scope:, root:)
17
+ log = Tempfile.new(['mutation_tester_tests', '.jsonl'])
18
+ log.close
19
+ outcome = yield(LOG_ENV => log.path, SCOPE_ENV => scope.to_s, ROOT_ENV => root.to_s)
20
+ [outcome, read(log.path)]
21
+ ensure
22
+ log&.unlink
23
+ end
24
+
25
+ def read(path)
26
+ return [] unless File.exist?(path)
27
+
28
+ File.readlines(path).filter_map do |line|
29
+ JSON.parse(line, symbolize_names: true)
30
+ rescue JSON::ParserError
31
+ nil
32
+ end
33
+ end
34
+
35
+ def failed_ids(entries)
36
+ Array(entries).select { |entry| entry[:status] == FAILED }.map { |entry| entry[:id] }.uniq.sort
37
+ end
38
+
39
+ def active?
40
+ !ENV[LOG_ENV].to_s.empty?
41
+ end
42
+
43
+ def write(entries)
44
+ return unless active?
45
+
46
+ kept = ENV[SCOPE_ENV] == 'failures' ? entries.select { |entry| entry[:status] == FAILED } : entries
47
+ return if kept.empty?
48
+
49
+ File.open(ENV[LOG_ENV], 'a') do |file|
50
+ kept.each { |entry| file.puts(JSON.generate(entry)) }
51
+ end
52
+ end
53
+
54
+ def relative_to_root(path)
55
+ root = ENV[ROOT_ENV].to_s
56
+ return path if root.empty?
57
+
58
+ prefixes = [root, resolved(root)].uniq.map { |candidate| "#{candidate.chomp('/')}/" }
59
+ [path, resolved(path)].uniq.each do |candidate|
60
+ prefix = prefixes.find { |value| candidate.start_with?(value) }
61
+ return candidate.delete_prefix(prefix) if prefix
62
+ end
63
+ path
64
+ end
65
+
66
+ private
67
+
68
+ def resolved(path)
69
+ File.realpath(path)
70
+ rescue SystemCallError
71
+ path
72
+ end
73
+ end
74
+ end
75
+ end
@@ -1,3 +1,3 @@
1
1
  module MutationTester
2
- VERSION = '1.5.0'.freeze
2
+ VERSION = '1.6.0'.freeze
3
3
  end
@@ -5,6 +5,7 @@ require 'rainbow'
5
5
  require 'fileutils'
6
6
  require 'erb'
7
7
  require 'json'
8
+ require 'io/console'
8
9
 
9
10
  require_relative 'mutation_tester/version'
10
11
  require_relative 'mutation_tester/configuration'