active_mutator 0.1.1 → 0.3.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 +4 -4
- data/README.md +156 -13
- data/lib/active_mutator/accepted_ledger.rb +22 -7
- data/lib/active_mutator/baseline_delta.rb +78 -1
- data/lib/active_mutator/class_shape.rb +47 -0
- data/lib/active_mutator/cli.rb +17 -4
- data/lib/active_mutator/closure_reload.rb +202 -0
- data/lib/active_mutator/config.rb +3 -1
- data/lib/active_mutator/config_file.rb +92 -0
- data/lib/active_mutator/defined_constants.rb +48 -0
- data/lib/active_mutator/edit.rb +8 -2
- data/lib/active_mutator/engine.rb +121 -7
- data/lib/active_mutator/inserter.rb +6 -3
- data/lib/active_mutator/operators/base.rb +2 -1
- data/lib/active_mutator/operators/call_swap.rb +16 -0
- data/lib/active_mutator/operators/literal.rb +14 -2
- data/lib/active_mutator/reporter/github.rb +36 -0
- data/lib/active_mutator/reporter/json.rb +1 -0
- data/lib/active_mutator/reporter/operator_stats.rb +20 -0
- data/lib/active_mutator/reporter/stryker_json.rb +128 -0
- data/lib/active_mutator/reporter/terminal.rb +24 -1
- data/lib/active_mutator/result.rb +1 -1
- data/lib/active_mutator/runner.rb +239 -19
- data/lib/active_mutator/scheduler.rb +41 -5
- data/lib/active_mutator/source_location.rb +21 -0
- data/lib/active_mutator/subject.rb +13 -3
- data/lib/active_mutator/subject_finder.rb +89 -9
- data/lib/active_mutator/subject_matcher.rb +23 -0
- data/lib/active_mutator/timeout_calibrator.rb +75 -0
- data/lib/active_mutator/version.rb +1 -1
- data/lib/active_mutator/work_item.rb +8 -1
- data/lib/active_mutator/worker.rb +53 -8
- data/lib/active_mutator.rb +10 -0
- metadata +20 -5
|
@@ -1,36 +1,88 @@
|
|
|
1
|
+
require "set"
|
|
2
|
+
|
|
1
3
|
module ActiveMutator
|
|
2
4
|
class SubjectFinder < Prism::Visitor
|
|
5
|
+
SKIP_MARKER = /#\s*active_mutator:\s*skip\b/
|
|
6
|
+
|
|
3
7
|
def self.call(file)
|
|
4
8
|
result = Prism.parse(File.read(file))
|
|
5
9
|
return [] unless result.success?
|
|
6
10
|
|
|
7
|
-
|
|
11
|
+
skip_lines = result.comments
|
|
12
|
+
.select { |c| c.slice.match?(SKIP_MARKER) }
|
|
13
|
+
.to_set { |c| c.location.start_line }
|
|
14
|
+
finder = new(file, skip_lines: skip_lines,
|
|
15
|
+
class_level: zeitwerk_shaped?(result.value))
|
|
8
16
|
finder.visit(result.value)
|
|
9
17
|
finder.subjects
|
|
10
18
|
end
|
|
11
19
|
|
|
20
|
+
# Class-body subjects only for Zeitwerk-shaped files: exactly one
|
|
21
|
+
# top-level constant. Multi-constant files and core-class reopens have no
|
|
22
|
+
# safe remove_const + re-eval story (issue #32). Shared with ClosureReload.
|
|
23
|
+
def self.zeitwerk_shaped?(program)
|
|
24
|
+
ClassShape.single_top_level_constant?(program)
|
|
25
|
+
end
|
|
26
|
+
|
|
12
27
|
attr_reader :subjects
|
|
13
28
|
|
|
14
|
-
def initialize(file)
|
|
29
|
+
def initialize(file, skip_lines: Set.new, class_level: true)
|
|
15
30
|
@file = file
|
|
31
|
+
@skip_lines = skip_lines
|
|
32
|
+
@class_level = class_level
|
|
16
33
|
@stack = []
|
|
17
34
|
@subjects = []
|
|
35
|
+
@sclass_depth = 0
|
|
18
36
|
super()
|
|
19
37
|
end
|
|
20
38
|
|
|
39
|
+
# Classes/modules declared inside `class << self` hang their constant on
|
|
40
|
+
# the SINGLETON class, so a lexically-joined scope like "Foo::Bar" is not
|
|
41
|
+
# reachable via Object.const_get — Inserter would crash. Skipped entirely.
|
|
21
42
|
def visit_class_node(node)
|
|
22
|
-
|
|
43
|
+
return if @sclass_depth.positive?
|
|
44
|
+
|
|
45
|
+
with_scope(node.constant_path.slice) do
|
|
46
|
+
add_class_body_subject(node)
|
|
47
|
+
super
|
|
48
|
+
end
|
|
23
49
|
end
|
|
24
50
|
|
|
25
51
|
def visit_module_node(node)
|
|
26
|
-
|
|
52
|
+
return if @sclass_depth.positive?
|
|
53
|
+
|
|
54
|
+
with_scope(node.constant_path.slice) do
|
|
55
|
+
add_class_body_subject(node)
|
|
56
|
+
super
|
|
57
|
+
end
|
|
27
58
|
end
|
|
28
59
|
|
|
29
|
-
# `class << self`
|
|
30
|
-
|
|
60
|
+
# `class << self` inside a constant scope: defs there are singleton
|
|
61
|
+
# methods of the enclosing constant. `class << obj` and a top-level
|
|
62
|
+
# `class << self` (no constant to hang the method on) stay skipped.
|
|
63
|
+
def visit_singleton_class_node(node)
|
|
64
|
+
return unless node.expression.is_a?(Prism::SelfNode) && !@stack.empty?
|
|
65
|
+
|
|
66
|
+
@sclass_depth += 1
|
|
67
|
+
begin
|
|
68
|
+
super
|
|
69
|
+
ensure
|
|
70
|
+
@sclass_depth -= 1
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Defs inside blocks (`Data.define do ... end`, `class_eval do ... end`)
|
|
75
|
+
# do not live on the enclosing constant scope, so Inserter would redefine
|
|
76
|
+
# them on the wrong constant and every mutant would falsely survive.
|
|
77
|
+
# Same v1 limit as `class << self`: not visited. Note this also hides
|
|
78
|
+
# classes/modules defined inside blocks (accepted v1 limit).
|
|
79
|
+
def visit_block_node(node); end
|
|
31
80
|
|
|
32
81
|
def visit_def_node(node)
|
|
33
|
-
|
|
82
|
+
return if @skip_lines.include?(node.location.start_line - 1)
|
|
83
|
+
|
|
84
|
+
sclass = @sclass_depth.positive?
|
|
85
|
+
singleton = sclass || node.receiver.is_a?(Prism::SelfNode)
|
|
34
86
|
scope = @stack.empty? ? nil : @stack.join("::")
|
|
35
87
|
loc = node.location
|
|
36
88
|
@subjects << Subject.new(
|
|
@@ -39,13 +91,41 @@ module ActiveMutator
|
|
|
39
91
|
byte_range: loc.start_offset...loc.end_offset,
|
|
40
92
|
line_range: loc.start_line..loc.end_line,
|
|
41
93
|
constant_scope: scope,
|
|
42
|
-
kind: singleton ? :singleton : :instance
|
|
94
|
+
kind: singleton ? :singleton : :instance,
|
|
95
|
+
sclass: sclass
|
|
43
96
|
)
|
|
44
|
-
# No `super`: nested defs
|
|
97
|
+
# No `super`: nested defs get no subject of their own -- their bodies
|
|
98
|
+
# are mutated via the OUTER def (Engine#walk descends into them).
|
|
45
99
|
end
|
|
46
100
|
|
|
47
101
|
private
|
|
48
102
|
|
|
103
|
+
# One subject for the class-level code of this class/module. Only if the
|
|
104
|
+
# body has at least one statement the class-body walk can mutate: defs
|
|
105
|
+
# and nested class/modules are owned by other subjects.
|
|
106
|
+
def add_class_body_subject(node)
|
|
107
|
+
return unless @class_level
|
|
108
|
+
return if @skip_lines.include?(node.location.start_line - 1)
|
|
109
|
+
|
|
110
|
+
body = node.body
|
|
111
|
+
return unless body.is_a?(Prism::StatementsNode)
|
|
112
|
+
return if body.body.all? { |s| owned_by_other_subject?(s) }
|
|
113
|
+
|
|
114
|
+
scope = @stack.join("::")
|
|
115
|
+
loc = node.location
|
|
116
|
+
@subjects << Subject.new(
|
|
117
|
+
name: "#{scope} (class body)",
|
|
118
|
+
file: @file,
|
|
119
|
+
byte_range: loc.start_offset...loc.end_offset,
|
|
120
|
+
line_range: loc.start_line..loc.end_line,
|
|
121
|
+
constant_scope: scope,
|
|
122
|
+
kind: :class_body,
|
|
123
|
+
sclass: false
|
|
124
|
+
)
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def owned_by_other_subject?(node) = ClassShape.owned_by_other_subject?(node)
|
|
128
|
+
|
|
49
129
|
def with_scope(name)
|
|
50
130
|
@stack.push(name)
|
|
51
131
|
yield
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
module ActiveMutator
|
|
2
|
+
# Tiny subject-expression grammar for --subject:
|
|
3
|
+
# Foo::Bar#baz exact Foo::Bar all methods of the constant
|
|
4
|
+
# Foo::Bar* namespace Foo::Bar#* instance-only Foo::Bar.* singleton-only
|
|
5
|
+
class SubjectMatcher
|
|
6
|
+
def initialize(expression)
|
|
7
|
+
@regexp = compile(expression)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def match?(name) = @regexp.match?(name)
|
|
11
|
+
|
|
12
|
+
private
|
|
13
|
+
|
|
14
|
+
def compile(expr)
|
|
15
|
+
case expr
|
|
16
|
+
when /\A(.+)([#.])\*\z/ then /\A#{Regexp.escape($1)}#{Regexp.escape($2)}[^#.]+\z/
|
|
17
|
+
when /\A(.+)\*\z/ then /\A#{Regexp.escape($1)}/
|
|
18
|
+
when /[#.]/ then /\A#{Regexp.escape(expr)}\z/
|
|
19
|
+
else /\A#{Regexp.escape(expr)}[#.][^#.]+\z/
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
module ActiveMutator
|
|
2
|
+
# Adaptive timeout budgets (#9). Static budgets derive from baseline times
|
|
3
|
+
# measured warm and unloaded; under parallel load they misclassify
|
|
4
|
+
# slow-but-honest kills as timeouts. The Scheduler feeds this with the
|
|
5
|
+
# observed wall time of every KILLED fork (errors exit artificially fast,
|
|
6
|
+
# survivors run their whole covering set — both would bias the median;
|
|
7
|
+
# timed-out forks have no known wall time at all). One instance per lane:
|
|
8
|
+
# parallel and serial run under different load regimes.
|
|
9
|
+
#
|
|
10
|
+
# Utilization is elapsed / the EFFECTIVE budget the fork ran under (the
|
|
11
|
+
# value budget_for returned at its spawn), not the static item.timeout —
|
|
12
|
+
# otherwise a pinned-high scale could never observe recovery (ratchet).
|
|
13
|
+
# Once WARMUP observations exist, remaining budgets' variable part is
|
|
14
|
+
# scaled by the clamped median utilization. The fixed part (timeout_floor
|
|
15
|
+
# + browser boot) is never scaled: fork boot cost does not shrink because
|
|
16
|
+
# examples run fast.
|
|
17
|
+
#
|
|
18
|
+
# The scale is grow-only (MIN_SCALE = 1.0): budgets may only extend beyond
|
|
19
|
+
# the static value, never fall below it. Downscaling has no recovery signal
|
|
20
|
+
# because timeouts are CENSORED samples — the scheduler skips them (they have
|
|
21
|
+
# no known wall time), so only killed forks feed the window. A low median
|
|
22
|
+
# utilization would shrink the budget, causing a legitimately slow kill to be
|
|
23
|
+
# reaped as a timeout; that censored kill never enters the window, so nothing
|
|
24
|
+
# ever pushes utilization back up. The shrink self-sustains — an asymmetric
|
|
25
|
+
# ratchet. Growing is safe (a too-large budget still records a real kill and
|
|
26
|
+
# relaxes); shrinking is not, so we forbid it.
|
|
27
|
+
class TimeoutCalibrator
|
|
28
|
+
WARMUP = 5
|
|
29
|
+
# A real sliding window, not full-run history: a 500-mutant run's early
|
|
30
|
+
# load regime must age out completely instead of anchoring the median
|
|
31
|
+
# forever. 30 samples is enough for a stable median and small enough to
|
|
32
|
+
# track load changes within a few dozen finishes.
|
|
33
|
+
WINDOW = 30
|
|
34
|
+
TARGET_UTILIZATION = 0.25
|
|
35
|
+
MIN_SCALE = 1.0
|
|
36
|
+
MAX_SCALE = 4.0
|
|
37
|
+
|
|
38
|
+
def initialize
|
|
39
|
+
@utilizations = []
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def record(elapsed_seconds, budget)
|
|
43
|
+
return unless budget.positive?
|
|
44
|
+
|
|
45
|
+
@utilizations << elapsed_seconds / budget
|
|
46
|
+
@utilizations.shift while @utilizations.size > WINDOW
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def warmed? = @utilizations.size >= WARMUP
|
|
50
|
+
|
|
51
|
+
def budget_for(item)
|
|
52
|
+
return item.timeout unless warmed?
|
|
53
|
+
|
|
54
|
+
fixed = item.timeout - item.variable
|
|
55
|
+
item.variable * scale + fixed
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def scale
|
|
59
|
+
# An empty window has no median; a caller invoking scale without warmed?
|
|
60
|
+
# (median of [] is nil) would otherwise raise. Neutral scale = no change.
|
|
61
|
+
return 1.0 if @utilizations.empty?
|
|
62
|
+
|
|
63
|
+
s = median(@utilizations) / TARGET_UTILIZATION
|
|
64
|
+
s.clamp(MIN_SCALE, MAX_SCALE)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
private
|
|
68
|
+
|
|
69
|
+
def median(values)
|
|
70
|
+
sorted = values.sort
|
|
71
|
+
mid = sorted.size / 2
|
|
72
|
+
sorted.size.odd? ? sorted[mid] : (sorted[mid - 1] + sorted[mid]) / 2.0
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
@@ -1,4 +1,11 @@
|
|
|
1
1
|
module ActiveMutator
|
|
2
2
|
# lane: :parallel (default pool) | :serial (browser-covered, one at a time)
|
|
3
|
-
|
|
3
|
+
# timeout: static total budget (variable + fixed), kept for --debug-plan and compat
|
|
4
|
+
# variable: the baseline-estimate-derived part (estimate * timeout_factor) — the
|
|
5
|
+
# only part the TimeoutCalibrator scales
|
|
6
|
+
WorkItem = Data.define(:mutation, :example_ids, :timeout, :lane, :variable) do
|
|
7
|
+
def initialize(mutation:, example_ids:, timeout:, lane:, variable: 0.0)
|
|
8
|
+
super
|
|
9
|
+
end
|
|
10
|
+
end
|
|
4
11
|
end
|
|
@@ -2,12 +2,32 @@ require "json"
|
|
|
2
2
|
require "set"
|
|
3
3
|
|
|
4
4
|
module ActiveMutator
|
|
5
|
-
# Runs INSIDE a fork.
|
|
6
|
-
# files,
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
# the
|
|
5
|
+
# Runs INSIDE a fork. Insertion order relative to RSpec's setup (which loads
|
|
6
|
+
# the spec files, and with them the app) DIFFERS by mutant kind:
|
|
7
|
+
#
|
|
8
|
+
# Class-body: insert BEFORE setup. `RSpec.describe SomeClass` binds
|
|
9
|
+
# `metadata[:described_class]` to the constant AT LOAD TIME, and a class-body
|
|
10
|
+
# mutant reloads the constant to a NEW object via ClosureReload; a group
|
|
11
|
+
# loaded first would keep the pre-mutation object and falsely survive. So we
|
|
12
|
+
# require the subject file, reload, THEN let setup load the groups — every
|
|
13
|
+
# group binds to the mutated object.
|
|
14
|
+
#
|
|
15
|
+
# Def: insert AFTER setup. A def mutant class_evals the live method in
|
|
16
|
+
# place, so it must be the LAST thing to touch that method. Inserting before
|
|
17
|
+
# setup let a file loaded during spec-load (a concern/decorator/monkeypatch
|
|
18
|
+
# that reopens the class but isn't transitively required by the subject
|
|
19
|
+
# file) silently redefine the method back to the original, reporting a false
|
|
20
|
+
# survivor. Loading everything first, then inserting, closes that window.
|
|
21
|
+
# Requiring the subject after setup also means spec_helper's load-time setup
|
|
22
|
+
# runs first, so a subject that depends on it still loads in non-preloaded
|
|
23
|
+
# projects.
|
|
24
|
+
#
|
|
25
|
+
# The explicit `require` of the subject file guarantees the target constant
|
|
26
|
+
# exists before insertion regardless of preload: preloaded projects
|
|
27
|
+
# (Rails/Zeitwerk, or a preloaded spec helper) already have it in
|
|
28
|
+
# $LOADED_FEATURES so it's a no-op, while non-preloaded projects (plain
|
|
29
|
+
# gems whose spec files require the lib themselves, or --no-preload-helper)
|
|
30
|
+
# get it loaded rather than relying on spec-load to define it.
|
|
11
31
|
class Worker
|
|
12
32
|
def self.run(mutation, example_ids, writer)
|
|
13
33
|
new(mutation, example_ids, writer).run
|
|
@@ -23,17 +43,42 @@ module ActiveMutator
|
|
|
23
43
|
require "rspec/core"
|
|
24
44
|
devnull = File.open(File::NULL, "w")
|
|
25
45
|
runner = RSpec::Core::Runner.new(RSpec::Core::ConfigurationOptions.new(@example_ids))
|
|
26
|
-
|
|
27
|
-
|
|
46
|
+
if @mutation.subject.class_body?
|
|
47
|
+
require @mutation.subject.file # no-op if already loaded; guarantees the constant exists
|
|
48
|
+
insert_mutation # BEFORE setup: groups bind described_class to the mutated object
|
|
49
|
+
runner.setup(devnull, devnull) # loads spec files
|
|
50
|
+
else
|
|
51
|
+
runner.setup(devnull, devnull) # loads spec files -> the app, in dependency order
|
|
52
|
+
require @mutation.subject.file # no-op if already loaded; guarantees the constant exists
|
|
53
|
+
insert_mutation # AFTER load: nothing left can redefine the method back
|
|
54
|
+
end
|
|
55
|
+
# One failure kills the mutant; running the rest of the covering set
|
|
56
|
+
# is pure waste inside the fork.
|
|
57
|
+
RSpec.configuration.fail_fast = 1
|
|
28
58
|
after_fork_hygiene
|
|
29
59
|
code = runner.run_specs(covering_groups)
|
|
30
60
|
emit(code.zero? ? "survived" : "killed")
|
|
61
|
+
rescue ClosureReload::Skip => e
|
|
62
|
+
emit("skipped", details: e.message)
|
|
63
|
+
rescue ClosureReload::MutantLoadError => e
|
|
64
|
+
# The mutation made the class unloadable; a real suite would fail on it.
|
|
65
|
+
emit("killed", details: "mutated class failed to load: #{e.message}")
|
|
31
66
|
rescue StandardError, ScriptError => e
|
|
32
67
|
emit("error", details: "#{e.class}: #{e.message}")
|
|
33
68
|
end
|
|
34
69
|
|
|
35
70
|
private
|
|
36
71
|
|
|
72
|
+
# Def mutants class_eval over the live constant; class-body mutants
|
|
73
|
+
# cannot (macros accumulate) and go through whole-file closure reload.
|
|
74
|
+
def insert_mutation
|
|
75
|
+
if @mutation.subject.class_body?
|
|
76
|
+
ClosureReload.new(@mutation.subject, @mutation.mutated_file_source).call
|
|
77
|
+
else
|
|
78
|
+
Inserter.new.insert(@mutation)
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
37
82
|
def after_fork_hygiene
|
|
38
83
|
srand
|
|
39
84
|
if defined?(ActiveRecord::Base)
|
data/lib/active_mutator.rb
CHANGED
|
@@ -10,7 +10,9 @@ end
|
|
|
10
10
|
require_relative "active_mutator/edit"
|
|
11
11
|
require_relative "active_mutator/splicer"
|
|
12
12
|
require_relative "active_mutator/subject"
|
|
13
|
+
require_relative "active_mutator/class_shape"
|
|
13
14
|
require_relative "active_mutator/subject_finder"
|
|
15
|
+
require_relative "active_mutator/subject_matcher"
|
|
14
16
|
require_relative "active_mutator/operators/base"
|
|
15
17
|
require_relative "active_mutator/operators/conditional_boundary"
|
|
16
18
|
require_relative "active_mutator/operators/condition_forcing"
|
|
@@ -27,16 +29,24 @@ require_relative "active_mutator/atomic_file"
|
|
|
27
29
|
require_relative "active_mutator/coverage_map"
|
|
28
30
|
require_relative "active_mutator/baseline"
|
|
29
31
|
require_relative "active_mutator/baseline_delta"
|
|
32
|
+
require_relative "active_mutator/defined_constants"
|
|
30
33
|
require_relative "active_mutator/inserter"
|
|
34
|
+
require_relative "active_mutator/closure_reload"
|
|
31
35
|
require_relative "active_mutator/worker"
|
|
32
36
|
require_relative "active_mutator/result"
|
|
33
37
|
require_relative "active_mutator/work_item"
|
|
38
|
+
require_relative "active_mutator/timeout_calibrator"
|
|
34
39
|
require_relative "active_mutator/scheduler"
|
|
40
|
+
require_relative "active_mutator/reporter/operator_stats"
|
|
35
41
|
require_relative "active_mutator/reporter/terminal"
|
|
42
|
+
require_relative "active_mutator/reporter/github"
|
|
36
43
|
require_relative "active_mutator/reporter/json"
|
|
44
|
+
require_relative "active_mutator/source_location"
|
|
37
45
|
require_relative "active_mutator/since_filter"
|
|
38
46
|
require_relative "active_mutator/fingerprint"
|
|
39
47
|
require_relative "active_mutator/accepted_ledger"
|
|
48
|
+
require_relative "active_mutator/reporter/stryker_json"
|
|
40
49
|
require_relative "active_mutator/config"
|
|
41
50
|
require_relative "active_mutator/runner"
|
|
51
|
+
require_relative "active_mutator/config_file"
|
|
42
52
|
require_relative "active_mutator/cli"
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: active_mutator
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Daniel John
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-07-
|
|
11
|
+
date: 2026-07-30 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: prism
|
|
@@ -17,6 +17,9 @@ dependencies:
|
|
|
17
17
|
- - ">="
|
|
18
18
|
- !ruby/object:Gem::Version
|
|
19
19
|
version: '0.30'
|
|
20
|
+
- - "<"
|
|
21
|
+
- !ruby/object:Gem::Version
|
|
22
|
+
version: '2'
|
|
20
23
|
type: :runtime
|
|
21
24
|
prerelease: false
|
|
22
25
|
version_requirements: !ruby/object:Gem::Requirement
|
|
@@ -24,18 +27,21 @@ dependencies:
|
|
|
24
27
|
- - ">="
|
|
25
28
|
- !ruby/object:Gem::Version
|
|
26
29
|
version: '0.30'
|
|
30
|
+
- - "<"
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '2'
|
|
27
33
|
- !ruby/object:Gem::Dependency
|
|
28
34
|
name: rspec-core
|
|
29
35
|
requirement: !ruby/object:Gem::Requirement
|
|
30
36
|
requirements:
|
|
31
|
-
- - "
|
|
37
|
+
- - "~>"
|
|
32
38
|
- !ruby/object:Gem::Version
|
|
33
39
|
version: '3.12'
|
|
34
40
|
type: :runtime
|
|
35
41
|
prerelease: false
|
|
36
42
|
version_requirements: !ruby/object:Gem::Requirement
|
|
37
43
|
requirements:
|
|
38
|
-
- - "
|
|
44
|
+
- - "~>"
|
|
39
45
|
- !ruby/object:Gem::Version
|
|
40
46
|
version: '3.12'
|
|
41
47
|
- !ruby/object:Gem::Dependency
|
|
@@ -72,9 +78,13 @@ files:
|
|
|
72
78
|
- lib/active_mutator/baseline.rb
|
|
73
79
|
- lib/active_mutator/baseline_delta.rb
|
|
74
80
|
- lib/active_mutator/baseline_hooks.rb
|
|
81
|
+
- lib/active_mutator/class_shape.rb
|
|
75
82
|
- lib/active_mutator/cli.rb
|
|
83
|
+
- lib/active_mutator/closure_reload.rb
|
|
76
84
|
- lib/active_mutator/config.rb
|
|
85
|
+
- lib/active_mutator/config_file.rb
|
|
77
86
|
- lib/active_mutator/coverage_map.rb
|
|
87
|
+
- lib/active_mutator/defined_constants.rb
|
|
78
88
|
- lib/active_mutator/edit.rb
|
|
79
89
|
- lib/active_mutator/engine.rb
|
|
80
90
|
- lib/active_mutator/fingerprint.rb
|
|
@@ -89,15 +99,21 @@ files:
|
|
|
89
99
|
- lib/active_mutator/operators/logical_operator.rb
|
|
90
100
|
- lib/active_mutator/operators/negation_removal.rb
|
|
91
101
|
- lib/active_mutator/operators/statement_deletion.rb
|
|
102
|
+
- lib/active_mutator/reporter/github.rb
|
|
92
103
|
- lib/active_mutator/reporter/json.rb
|
|
104
|
+
- lib/active_mutator/reporter/operator_stats.rb
|
|
105
|
+
- lib/active_mutator/reporter/stryker_json.rb
|
|
93
106
|
- lib/active_mutator/reporter/terminal.rb
|
|
94
107
|
- lib/active_mutator/result.rb
|
|
95
108
|
- lib/active_mutator/runner.rb
|
|
96
109
|
- lib/active_mutator/scheduler.rb
|
|
97
110
|
- lib/active_mutator/since_filter.rb
|
|
111
|
+
- lib/active_mutator/source_location.rb
|
|
98
112
|
- lib/active_mutator/splicer.rb
|
|
99
113
|
- lib/active_mutator/subject.rb
|
|
100
114
|
- lib/active_mutator/subject_finder.rb
|
|
115
|
+
- lib/active_mutator/subject_matcher.rb
|
|
116
|
+
- lib/active_mutator/timeout_calibrator.rb
|
|
101
117
|
- lib/active_mutator/version.rb
|
|
102
118
|
- lib/active_mutator/work_item.rb
|
|
103
119
|
- lib/active_mutator/worker.rb
|
|
@@ -106,7 +122,6 @@ licenses:
|
|
|
106
122
|
- MIT
|
|
107
123
|
metadata:
|
|
108
124
|
rubygems_mfa_required: 'true'
|
|
109
|
-
homepage_uri: https://github.com/drj613/active_mutator
|
|
110
125
|
source_code_uri: https://github.com/drj613/active_mutator
|
|
111
126
|
changelog_uri: https://github.com/drj613/active_mutator/blob/main/CHANGELOG.md
|
|
112
127
|
bug_tracker_uri: https://github.com/drj613/active_mutator/issues
|