active_mutator 0.1.1 → 0.2.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,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,3 +1,3 @@
1
1
  module ActiveMutator
2
- VERSION = "0.1.1"
2
+ VERSION = "0.2.0"
3
3
  end
@@ -1,4 +1,11 @@
1
1
  module ActiveMutator
2
2
  # lane: :parallel (default pool) | :serial (browser-covered, one at a time)
3
- WorkItem = Data.define(:mutation, :example_ids, :timeout, :lane)
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
@@ -24,6 +24,9 @@ module ActiveMutator
24
24
  devnull = File.open(File::NULL, "w")
25
25
  runner = RSpec::Core::Runner.new(RSpec::Core::ConfigurationOptions.new(@example_ids))
26
26
  runner.setup(devnull, devnull) # loads spec files -> loads the app
27
+ # One failure kills the mutant; running the rest of the covering set
28
+ # is pure waste inside the fork.
29
+ RSpec.configuration.fail_fast = 1
27
30
  Inserter.new.insert(@mutation) # now the target constant exists
28
31
  after_fork_hygiene
29
32
  code = runner.run_specs(covering_groups)
@@ -11,6 +11,7 @@ require_relative "active_mutator/edit"
11
11
  require_relative "active_mutator/splicer"
12
12
  require_relative "active_mutator/subject"
13
13
  require_relative "active_mutator/subject_finder"
14
+ require_relative "active_mutator/subject_matcher"
14
15
  require_relative "active_mutator/operators/base"
15
16
  require_relative "active_mutator/operators/conditional_boundary"
16
17
  require_relative "active_mutator/operators/condition_forcing"
@@ -27,16 +28,23 @@ require_relative "active_mutator/atomic_file"
27
28
  require_relative "active_mutator/coverage_map"
28
29
  require_relative "active_mutator/baseline"
29
30
  require_relative "active_mutator/baseline_delta"
31
+ require_relative "active_mutator/defined_constants"
30
32
  require_relative "active_mutator/inserter"
31
33
  require_relative "active_mutator/worker"
32
34
  require_relative "active_mutator/result"
33
35
  require_relative "active_mutator/work_item"
36
+ require_relative "active_mutator/timeout_calibrator"
34
37
  require_relative "active_mutator/scheduler"
38
+ require_relative "active_mutator/reporter/operator_stats"
35
39
  require_relative "active_mutator/reporter/terminal"
40
+ require_relative "active_mutator/reporter/github"
36
41
  require_relative "active_mutator/reporter/json"
42
+ require_relative "active_mutator/source_location"
37
43
  require_relative "active_mutator/since_filter"
38
44
  require_relative "active_mutator/fingerprint"
39
45
  require_relative "active_mutator/accepted_ledger"
46
+ require_relative "active_mutator/reporter/stryker_json"
40
47
  require_relative "active_mutator/config"
41
48
  require_relative "active_mutator/runner"
49
+ require_relative "active_mutator/config_file"
42
50
  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.1.1
4
+ version: 0.2.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-14 00:00:00.000000000 Z
11
+ date: 2026-07-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: prism
@@ -74,7 +74,9 @@ files:
74
74
  - lib/active_mutator/baseline_hooks.rb
75
75
  - lib/active_mutator/cli.rb
76
76
  - lib/active_mutator/config.rb
77
+ - lib/active_mutator/config_file.rb
77
78
  - lib/active_mutator/coverage_map.rb
79
+ - lib/active_mutator/defined_constants.rb
78
80
  - lib/active_mutator/edit.rb
79
81
  - lib/active_mutator/engine.rb
80
82
  - lib/active_mutator/fingerprint.rb
@@ -89,15 +91,21 @@ files:
89
91
  - lib/active_mutator/operators/logical_operator.rb
90
92
  - lib/active_mutator/operators/negation_removal.rb
91
93
  - lib/active_mutator/operators/statement_deletion.rb
94
+ - lib/active_mutator/reporter/github.rb
92
95
  - lib/active_mutator/reporter/json.rb
96
+ - lib/active_mutator/reporter/operator_stats.rb
97
+ - lib/active_mutator/reporter/stryker_json.rb
93
98
  - lib/active_mutator/reporter/terminal.rb
94
99
  - lib/active_mutator/result.rb
95
100
  - lib/active_mutator/runner.rb
96
101
  - lib/active_mutator/scheduler.rb
97
102
  - lib/active_mutator/since_filter.rb
103
+ - lib/active_mutator/source_location.rb
98
104
  - lib/active_mutator/splicer.rb
99
105
  - lib/active_mutator/subject.rb
100
106
  - lib/active_mutator/subject_finder.rb
107
+ - lib/active_mutator/subject_matcher.rb
108
+ - lib/active_mutator/timeout_calibrator.rb
101
109
  - lib/active_mutator/version.rb
102
110
  - lib/active_mutator/work_item.rb
103
111
  - lib/active_mutator/worker.rb