active_mutator 0.1.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
- data/LICENSE +21 -0
- data/README.md +244 -0
- data/exe/active_mutator +4 -0
- data/lib/active_mutator/accepted_ledger.rb +45 -0
- data/lib/active_mutator/analysis.rb +3 -0
- data/lib/active_mutator/atomic_file.rb +15 -0
- data/lib/active_mutator/baseline.rb +125 -0
- data/lib/active_mutator/baseline_delta.rb +57 -0
- data/lib/active_mutator/baseline_hooks.rb +68 -0
- data/lib/active_mutator/cli.rb +53 -0
- data/lib/active_mutator/config.rb +8 -0
- data/lib/active_mutator/coverage_map.rb +55 -0
- data/lib/active_mutator/edit.rb +5 -0
- data/lib/active_mutator/engine.rb +75 -0
- data/lib/active_mutator/fingerprint.rb +21 -0
- data/lib/active_mutator/inserter.rb +18 -0
- data/lib/active_mutator/mutation.rb +11 -0
- data/lib/active_mutator/operators/base.rb +25 -0
- data/lib/active_mutator/operators/call_swap.rb +28 -0
- data/lib/active_mutator/operators/condition_forcing.rb +17 -0
- data/lib/active_mutator/operators/conditional_boundary.rb +16 -0
- data/lib/active_mutator/operators/early_return.rb +16 -0
- data/lib/active_mutator/operators/literal.rb +37 -0
- data/lib/active_mutator/operators/logical_operator.rb +24 -0
- data/lib/active_mutator/operators/negation_removal.rb +11 -0
- data/lib/active_mutator/operators/statement_deletion.rb +15 -0
- data/lib/active_mutator/reporter/json.rb +40 -0
- data/lib/active_mutator/reporter/terminal.rb +50 -0
- data/lib/active_mutator/result.rb +6 -0
- data/lib/active_mutator/runner.rb +143 -0
- data/lib/active_mutator/scheduler.rb +125 -0
- data/lib/active_mutator/since_filter.rb +48 -0
- data/lib/active_mutator/splicer.rb +13 -0
- data/lib/active_mutator/subject.rb +7 -0
- data/lib/active_mutator/subject_finder.rb +56 -0
- data/lib/active_mutator/version.rb +3 -0
- data/lib/active_mutator/work_item.rb +4 -0
- data/lib/active_mutator/worker.rb +64 -0
- data/lib/active_mutator.rb +42 -0
- metadata +132 -0
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
require "json"
|
|
2
|
+
|
|
3
|
+
module ActiveMutator
|
|
4
|
+
# Fork pool: one fork per WorkItem, capped at `jobs` concurrent forks.
|
|
5
|
+
# Parent enforces per-item deadlines with SIGKILL (worker-side timeouts
|
|
6
|
+
# cannot interrupt all infinite loops).
|
|
7
|
+
class Scheduler
|
|
8
|
+
def initialize(jobs:, worker: Worker.method(:run), on_result: nil)
|
|
9
|
+
@jobs = jobs
|
|
10
|
+
@worker = worker
|
|
11
|
+
@on_result = on_result
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def run(items)
|
|
15
|
+
previous_traps = nil
|
|
16
|
+
running = {}
|
|
17
|
+
previous_traps = install_signal_handlers(running)
|
|
18
|
+
results = []
|
|
19
|
+
# Browser-covered mutants each boot Chrome + an app server; running them
|
|
20
|
+
# concurrently melts CPUs and manufactures false timeouts. Parallel lane
|
|
21
|
+
# first at full width, then the serial lane one at a time.
|
|
22
|
+
results.concat(run_pool(items.select { |i| i.lane == :parallel }, @jobs, running))
|
|
23
|
+
results.concat(run_pool(items.select { |i| i.lane == :serial }, 1, running))
|
|
24
|
+
results
|
|
25
|
+
ensure
|
|
26
|
+
restore_traps(previous_traps) if previous_traps
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
def run_pool(items, width, running)
|
|
32
|
+
queue = items.dup
|
|
33
|
+
results = []
|
|
34
|
+
until queue.empty? && running.empty?
|
|
35
|
+
spawn(queue.shift, running) while running.size < width && !queue.empty?
|
|
36
|
+
reap(running, results)
|
|
37
|
+
sleep 0.02 unless running.empty?
|
|
38
|
+
end
|
|
39
|
+
results
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def spawn(item, running)
|
|
43
|
+
reader, writer = IO.pipe
|
|
44
|
+
pid = fork do
|
|
45
|
+
reader.close
|
|
46
|
+
Process.setpgid(0, 0) # own process group: deadline kill reaps grandchildren too
|
|
47
|
+
$stdout.reopen(File::NULL) # app code that prints must not corrupt parent's report
|
|
48
|
+
$stderr.reopen(File::NULL) # ditto for warnings (RSpec/app noise interleaves with reports)
|
|
49
|
+
@worker.call(item.mutation, item.example_ids, writer)
|
|
50
|
+
writer.close
|
|
51
|
+
Process.exit!(0)
|
|
52
|
+
end
|
|
53
|
+
writer.close
|
|
54
|
+
running[pid] = { reader: reader, item: item, deadline: now + item.timeout }
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def reap(running, results)
|
|
58
|
+
running.to_a.each do |pid, entry|
|
|
59
|
+
done, _status = Process.waitpid2(pid, Process::WNOHANG)
|
|
60
|
+
if done
|
|
61
|
+
running.delete(pid)
|
|
62
|
+
results << finish(entry)
|
|
63
|
+
elsif now > entry[:deadline]
|
|
64
|
+
kill(pid)
|
|
65
|
+
running.delete(pid)
|
|
66
|
+
entry[:reader].close
|
|
67
|
+
results << report(Result.new(mutation: entry[:item].mutation, status: :timeout, details: nil))
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def finish(entry)
|
|
73
|
+
payload = entry[:reader].read.to_s
|
|
74
|
+
entry[:reader].close
|
|
75
|
+
data = payload.empty? ? nil : JSON.parse(payload)
|
|
76
|
+
status = data ? data.fetch("status").to_sym : :error
|
|
77
|
+
details = data ? data["details"] : "worker exited without reporting"
|
|
78
|
+
report(Result.new(mutation: entry[:item].mutation, status: status, details: details))
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def report(result)
|
|
82
|
+
@on_result&.call(result)
|
|
83
|
+
result
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def kill(pid)
|
|
87
|
+
Process.kill("KILL", -pid) # negative pid = whole process group
|
|
88
|
+
rescue Errno::ESRCH, Errno::EPERM
|
|
89
|
+
# Group not established yet (setpgid race) or already gone — direct kill.
|
|
90
|
+
begin
|
|
91
|
+
Process.kill("KILL", pid)
|
|
92
|
+
rescue Errno::ESRCH
|
|
93
|
+
nil
|
|
94
|
+
end
|
|
95
|
+
ensure
|
|
96
|
+
begin
|
|
97
|
+
Process.waitpid(pid)
|
|
98
|
+
rescue Errno::ECHILD
|
|
99
|
+
nil
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# Returns {sig => previous_handler} so #run can restore on exit —
|
|
104
|
+
# otherwise our traps permanently replace the host's (e.g. RSpec's Ctrl-C).
|
|
105
|
+
def install_signal_handlers(running)
|
|
106
|
+
%w[INT TERM].to_h do |sig|
|
|
107
|
+
previous = trap(sig) do
|
|
108
|
+
running.each_key do |pid|
|
|
109
|
+
Process.kill("KILL", -pid)
|
|
110
|
+
rescue StandardError
|
|
111
|
+
nil
|
|
112
|
+
end
|
|
113
|
+
exit(130)
|
|
114
|
+
end
|
|
115
|
+
[sig, previous]
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def restore_traps(previous)
|
|
120
|
+
previous.each { |sig, handler| trap(sig, handler || "DEFAULT") }
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def now = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
124
|
+
end
|
|
125
|
+
end
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
module ActiveMutator
|
|
2
|
+
# Restricts subjects to methods overlapping lines changed since a git ref.
|
|
3
|
+
# Known v1 limit: `git diff <ref>` omits untracked files, so brand-new
|
|
4
|
+
# uncommitted files are skipped.
|
|
5
|
+
class SinceFilter
|
|
6
|
+
HUNK = /\A@@ [^+]*\+(\d+)(?:,(\d+))? @@/
|
|
7
|
+
|
|
8
|
+
def self.parse(diff_text)
|
|
9
|
+
changed = Hash.new { |h, k| h[k] = [] }
|
|
10
|
+
current = nil
|
|
11
|
+
diff_text.each_line do |line|
|
|
12
|
+
if line.start_with?("+++ b/")
|
|
13
|
+
current = line.delete_prefix("+++ b/").strip
|
|
14
|
+
elsif current && (match = HUNK.match(line))
|
|
15
|
+
start = match[1].to_i
|
|
16
|
+
count = (match[2] || "1").to_i
|
|
17
|
+
count.times { |i| changed[current] << start + i }
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
changed.reject { |_, lines| lines.empty? }
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def initialize(ref:, root:)
|
|
24
|
+
@root = root
|
|
25
|
+
diff = IO.popen(
|
|
26
|
+
["git", "-C", root, "diff", "--unified=0", ref, "--", "*.rb"], &:read
|
|
27
|
+
)
|
|
28
|
+
raise Error, "git diff #{ref} failed" unless $?.success?
|
|
29
|
+
|
|
30
|
+
@changed = self.class.parse(diff)
|
|
31
|
+
untracked = IO.popen(
|
|
32
|
+
["git", "-C", root, "ls-files", "--others", "--exclude-standard", "--", "*.rb"], &:read
|
|
33
|
+
)
|
|
34
|
+
# Untracked files are invisible to `git diff` but are agentic TDD's most
|
|
35
|
+
# common case (brand-new file + spec). Whole-file sentinel: every line
|
|
36
|
+
# counts as changed.
|
|
37
|
+
untracked.each_line { |l| @changed[l.strip] = :all unless l.strip.empty? }
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def cover?(subject)
|
|
41
|
+
lines = @changed[subject.file.delete_prefix("#{@root}/")]
|
|
42
|
+
return false unless lines
|
|
43
|
+
return true if lines == :all
|
|
44
|
+
|
|
45
|
+
lines.any? { |line| subject.line_range.cover?(line) }
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
module ActiveMutator
|
|
2
|
+
module Splicer
|
|
3
|
+
# Applies edits to source by byte offset. Edits are applied back-to-front
|
|
4
|
+
# so earlier offsets never drift.
|
|
5
|
+
def self.apply(source, edits)
|
|
6
|
+
bytes = source.b
|
|
7
|
+
edits.sort_by { |e| -e.range.begin }.each do |e|
|
|
8
|
+
bytes[e.range] = e.replacement.b
|
|
9
|
+
end
|
|
10
|
+
bytes.force_encoding(source.encoding)
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
module ActiveMutator
|
|
2
|
+
class SubjectFinder < Prism::Visitor
|
|
3
|
+
def self.call(file)
|
|
4
|
+
result = Prism.parse(File.read(file))
|
|
5
|
+
return [] unless result.success?
|
|
6
|
+
|
|
7
|
+
finder = new(file)
|
|
8
|
+
finder.visit(result.value)
|
|
9
|
+
finder.subjects
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
attr_reader :subjects
|
|
13
|
+
|
|
14
|
+
def initialize(file)
|
|
15
|
+
@file = file
|
|
16
|
+
@stack = []
|
|
17
|
+
@subjects = []
|
|
18
|
+
super()
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def visit_class_node(node)
|
|
22
|
+
with_scope(node.constant_path.slice) { super }
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def visit_module_node(node)
|
|
26
|
+
with_scope(node.constant_path.slice) { super }
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# `class << self` bodies are a documented v1 limit: not visited.
|
|
30
|
+
def visit_singleton_class_node(node); end
|
|
31
|
+
|
|
32
|
+
def visit_def_node(node)
|
|
33
|
+
singleton = node.receiver.is_a?(Prism::SelfNode)
|
|
34
|
+
scope = @stack.empty? ? nil : @stack.join("::")
|
|
35
|
+
loc = node.location
|
|
36
|
+
@subjects << Subject.new(
|
|
37
|
+
name: "#{scope || "Object"}#{singleton ? "." : "#"}#{node.name}",
|
|
38
|
+
file: @file,
|
|
39
|
+
byte_range: loc.start_offset...loc.end_offset,
|
|
40
|
+
line_range: loc.start_line..loc.end_line,
|
|
41
|
+
constant_scope: scope,
|
|
42
|
+
kind: singleton ? :singleton : :instance
|
|
43
|
+
)
|
|
44
|
+
# No `super`: nested defs are out of scope for v1.
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
private
|
|
48
|
+
|
|
49
|
+
def with_scope(name)
|
|
50
|
+
@stack.push(name)
|
|
51
|
+
yield
|
|
52
|
+
ensure
|
|
53
|
+
@stack.pop
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
require "json"
|
|
2
|
+
require "set"
|
|
3
|
+
|
|
4
|
+
module ActiveMutator
|
|
5
|
+
# Runs INSIDE a fork. Order is critical: RSpec's setup phase loads the spec
|
|
6
|
+
# files, whose spec_helper/rails_helper loads the application — only THEN
|
|
7
|
+
# can the mutation be inserted over the loaded original. Insert-first would
|
|
8
|
+
# NameError on any project not preloaded in the parent (all non-Rails
|
|
9
|
+
# projects), and loading app code after insertion would silently restore
|
|
10
|
+
# the original method.
|
|
11
|
+
class Worker
|
|
12
|
+
def self.run(mutation, example_ids, writer)
|
|
13
|
+
new(mutation, example_ids, writer).run
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def initialize(mutation, example_ids, writer)
|
|
17
|
+
@mutation = mutation
|
|
18
|
+
@example_ids = example_ids
|
|
19
|
+
@writer = writer
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def run
|
|
23
|
+
require "rspec/core"
|
|
24
|
+
devnull = File.open(File::NULL, "w")
|
|
25
|
+
runner = RSpec::Core::Runner.new(RSpec::Core::ConfigurationOptions.new(@example_ids))
|
|
26
|
+
runner.setup(devnull, devnull) # loads spec files -> loads the app
|
|
27
|
+
Inserter.new.insert(@mutation) # now the target constant exists
|
|
28
|
+
after_fork_hygiene
|
|
29
|
+
code = runner.run_specs(covering_groups)
|
|
30
|
+
emit(code.zero? ? "survived" : "killed")
|
|
31
|
+
rescue StandardError, ScriptError => e
|
|
32
|
+
emit("error", details: "#{e.class}: #{e.message}")
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
private
|
|
36
|
+
|
|
37
|
+
def after_fork_hygiene
|
|
38
|
+
srand
|
|
39
|
+
if defined?(ActiveRecord::Base)
|
|
40
|
+
ActiveRecord::Base.connection_handler.clear_all_connections!
|
|
41
|
+
ActiveRecord::Base.establish_connection
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def emit(status, details: nil)
|
|
46
|
+
@writer.puts(JSON.generate("status" => status, "details" => details))
|
|
47
|
+
@writer.flush if @writer.respond_to?(:flush)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# RSpec.world holds every group registered in the process — including any
|
|
51
|
+
# top-level groups evaluated while the PARENT preloaded the spec helper
|
|
52
|
+
# (spec/support files with RSpec.describe at load time are common). Those
|
|
53
|
+
# leak into the fork; running them would report their failures as false
|
|
54
|
+
# kills. Run only groups that belong to the covering spec files.
|
|
55
|
+
def covering_groups
|
|
56
|
+
covering = @example_ids
|
|
57
|
+
.map { |id| File.expand_path(id[/\A(.+?)\[/, 1]) }
|
|
58
|
+
.to_set
|
|
59
|
+
RSpec.world.ordered_example_groups.select do |group|
|
|
60
|
+
covering.include?(group.metadata[:absolute_file_path])
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
require "prism"
|
|
2
|
+
|
|
3
|
+
require_relative "active_mutator/version"
|
|
4
|
+
|
|
5
|
+
module ActiveMutator
|
|
6
|
+
Error = Class.new(StandardError)
|
|
7
|
+
BaselineFailed = Class.new(Error)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
require_relative "active_mutator/edit"
|
|
11
|
+
require_relative "active_mutator/splicer"
|
|
12
|
+
require_relative "active_mutator/subject"
|
|
13
|
+
require_relative "active_mutator/subject_finder"
|
|
14
|
+
require_relative "active_mutator/operators/base"
|
|
15
|
+
require_relative "active_mutator/operators/conditional_boundary"
|
|
16
|
+
require_relative "active_mutator/operators/condition_forcing"
|
|
17
|
+
require_relative "active_mutator/operators/logical_operator"
|
|
18
|
+
require_relative "active_mutator/operators/literal"
|
|
19
|
+
require_relative "active_mutator/operators/statement_deletion"
|
|
20
|
+
require_relative "active_mutator/operators/early_return"
|
|
21
|
+
require_relative "active_mutator/operators/call_swap"
|
|
22
|
+
require_relative "active_mutator/operators/negation_removal"
|
|
23
|
+
require_relative "active_mutator/mutation"
|
|
24
|
+
require_relative "active_mutator/analysis"
|
|
25
|
+
require_relative "active_mutator/engine"
|
|
26
|
+
require_relative "active_mutator/atomic_file"
|
|
27
|
+
require_relative "active_mutator/coverage_map"
|
|
28
|
+
require_relative "active_mutator/baseline"
|
|
29
|
+
require_relative "active_mutator/baseline_delta"
|
|
30
|
+
require_relative "active_mutator/inserter"
|
|
31
|
+
require_relative "active_mutator/worker"
|
|
32
|
+
require_relative "active_mutator/result"
|
|
33
|
+
require_relative "active_mutator/work_item"
|
|
34
|
+
require_relative "active_mutator/scheduler"
|
|
35
|
+
require_relative "active_mutator/reporter/terminal"
|
|
36
|
+
require_relative "active_mutator/reporter/json"
|
|
37
|
+
require_relative "active_mutator/since_filter"
|
|
38
|
+
require_relative "active_mutator/fingerprint"
|
|
39
|
+
require_relative "active_mutator/accepted_ledger"
|
|
40
|
+
require_relative "active_mutator/config"
|
|
41
|
+
require_relative "active_mutator/runner"
|
|
42
|
+
require_relative "active_mutator/cli"
|
metadata
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: active_mutator
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Daniel John
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: exe
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2026-07-10 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: prism
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - ">="
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '0.30'
|
|
20
|
+
type: :runtime
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - ">="
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '0.30'
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: rspec-core
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - ">="
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '3.12'
|
|
34
|
+
type: :runtime
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - ">="
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '3.12'
|
|
41
|
+
- !ruby/object:Gem::Dependency
|
|
42
|
+
name: rspec
|
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
|
44
|
+
requirements:
|
|
45
|
+
- - "~>"
|
|
46
|
+
- !ruby/object:Gem::Version
|
|
47
|
+
version: '3.13'
|
|
48
|
+
type: :development
|
|
49
|
+
prerelease: false
|
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
51
|
+
requirements:
|
|
52
|
+
- - "~>"
|
|
53
|
+
- !ruby/object:Gem::Version
|
|
54
|
+
version: '3.13'
|
|
55
|
+
description: 'Mutation testing for Ruby and Rails: Prism-based source-span mutations
|
|
56
|
+
(no unparser), coverage-mapped test selection, and a fork-per-mutant kill pipeline.
|
|
57
|
+
Scopes to changed methods for a fast dev loop, or a diff for CI, with an incremental
|
|
58
|
+
coverage baseline and a committed acceptance ledger for equivalent mutants.'
|
|
59
|
+
email:
|
|
60
|
+
executables:
|
|
61
|
+
- active_mutator
|
|
62
|
+
extensions: []
|
|
63
|
+
extra_rdoc_files: []
|
|
64
|
+
files:
|
|
65
|
+
- LICENSE
|
|
66
|
+
- README.md
|
|
67
|
+
- exe/active_mutator
|
|
68
|
+
- lib/active_mutator.rb
|
|
69
|
+
- lib/active_mutator/accepted_ledger.rb
|
|
70
|
+
- lib/active_mutator/analysis.rb
|
|
71
|
+
- lib/active_mutator/atomic_file.rb
|
|
72
|
+
- lib/active_mutator/baseline.rb
|
|
73
|
+
- lib/active_mutator/baseline_delta.rb
|
|
74
|
+
- lib/active_mutator/baseline_hooks.rb
|
|
75
|
+
- lib/active_mutator/cli.rb
|
|
76
|
+
- lib/active_mutator/config.rb
|
|
77
|
+
- lib/active_mutator/coverage_map.rb
|
|
78
|
+
- lib/active_mutator/edit.rb
|
|
79
|
+
- lib/active_mutator/engine.rb
|
|
80
|
+
- lib/active_mutator/fingerprint.rb
|
|
81
|
+
- lib/active_mutator/inserter.rb
|
|
82
|
+
- lib/active_mutator/mutation.rb
|
|
83
|
+
- lib/active_mutator/operators/base.rb
|
|
84
|
+
- lib/active_mutator/operators/call_swap.rb
|
|
85
|
+
- lib/active_mutator/operators/condition_forcing.rb
|
|
86
|
+
- lib/active_mutator/operators/conditional_boundary.rb
|
|
87
|
+
- lib/active_mutator/operators/early_return.rb
|
|
88
|
+
- lib/active_mutator/operators/literal.rb
|
|
89
|
+
- lib/active_mutator/operators/logical_operator.rb
|
|
90
|
+
- lib/active_mutator/operators/negation_removal.rb
|
|
91
|
+
- lib/active_mutator/operators/statement_deletion.rb
|
|
92
|
+
- lib/active_mutator/reporter/json.rb
|
|
93
|
+
- lib/active_mutator/reporter/terminal.rb
|
|
94
|
+
- lib/active_mutator/result.rb
|
|
95
|
+
- lib/active_mutator/runner.rb
|
|
96
|
+
- lib/active_mutator/scheduler.rb
|
|
97
|
+
- lib/active_mutator/since_filter.rb
|
|
98
|
+
- lib/active_mutator/splicer.rb
|
|
99
|
+
- lib/active_mutator/subject.rb
|
|
100
|
+
- lib/active_mutator/subject_finder.rb
|
|
101
|
+
- lib/active_mutator/version.rb
|
|
102
|
+
- lib/active_mutator/work_item.rb
|
|
103
|
+
- lib/active_mutator/worker.rb
|
|
104
|
+
homepage: https://github.com/drj613/active_mutator
|
|
105
|
+
licenses:
|
|
106
|
+
- MIT
|
|
107
|
+
metadata:
|
|
108
|
+
rubygems_mfa_required: 'true'
|
|
109
|
+
homepage_uri: https://github.com/drj613/active_mutator
|
|
110
|
+
source_code_uri: https://github.com/drj613/active_mutator
|
|
111
|
+
changelog_uri: https://github.com/drj613/active_mutator/blob/main/CHANGELOG.md
|
|
112
|
+
bug_tracker_uri: https://github.com/drj613/active_mutator/issues
|
|
113
|
+
post_install_message:
|
|
114
|
+
rdoc_options: []
|
|
115
|
+
require_paths:
|
|
116
|
+
- lib
|
|
117
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
118
|
+
requirements:
|
|
119
|
+
- - ">="
|
|
120
|
+
- !ruby/object:Gem::Version
|
|
121
|
+
version: '3.2'
|
|
122
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
123
|
+
requirements:
|
|
124
|
+
- - ">="
|
|
125
|
+
- !ruby/object:Gem::Version
|
|
126
|
+
version: '0'
|
|
127
|
+
requirements: []
|
|
128
|
+
rubygems_version: 3.5.16
|
|
129
|
+
signing_key:
|
|
130
|
+
specification_version: 4
|
|
131
|
+
summary: Mutation testing for Ruby, built on Prism
|
|
132
|
+
test_files: []
|