Module: Evilution::Feedback::SetupWarning

Defined in:
lib/evilution/feedback/setup_warning.rb

Overview

Detects “setup misconfiguration” patterns where mutation testing returned a result that is technically valid (score 0.0 with all-errored mutations) but is almost certainly the wrong score because the worker process couldn’t evaluate any mutated source.

Most common cause: MCP runs default to ‘preload: false` to keep the long-lived MCP server clean. Rails / Zeitwerk projects that depend on autoload then fail in every worker with `NameError: uninitialized constant …`. The user sees “0% PASS-but-FAIL” with no obvious hint that they need to pass an explicit `preload: spec/rails_helper.rb` option.

When triggered, this returns a warning string the MCP response can surface alongside the trimmed report — turning a silent wrong score into a loud pointer at the likely fix.

Constant Summary collapse

ERROR_DOMINANCE_THRESHOLD =
0.8
ERROR_CLASS_CLUSTER_THRESHOLD =
0.8
NAME_ERROR_HINT =
"Most mutations errored with NameError. This usually means autoloaded constants " \
"(Rails / Zeitwerk) weren't available when the mutation re-evaluated the source. " \
"Pass `preload: 'spec/rails_helper.rb'` (or your project's preload entry) so the " \
"MCP server requires it before forking workers."
LOAD_ERROR_HINT =
"Most mutations errored with LoadError. A `require` in the mutated source path " \
"failed before any test ran. Check that the file's dependencies are reachable from " \
"the MCP server's load path, or pass `preload: '<entrypoint>'` to set them up."
GENERIC_HINT_TEMPLATE =
"Most mutations errored with %<klass>s (%<count>d / %<total>d). The mutation " \
"score reflects this setup failure, not the test suite. Try the CLI for an " \
"independent reading, or pass `preload: '<path>'` if the failure is autoload-related."

Class Method Summary collapse

Class Method Details

.call(summary) ⇒ Object



25
26
27
28
29
30
31
32
33
34
# File 'lib/evilution/feedback/setup_warning.rb', line 25

def call(summary)
  return nil if summary.nil?
  return nil unless errors_dominate?(summary)

  errored = summary.results.select(&:error?)
  dominant_class = dominant_error_class(errored)
  return nil unless dominant_class

  message_for(dominant_class, errored.size, summary.total)
end