Mutare.Report.Live (mutare v0.1.1)

Copy Markdown View Source

Live progress for the human report.

Progress is written to stderr so stdout remains safe for the final or machine-readable report. An interactive terminal gets a spinner, current mutant, counts, and ETA; pipes and CI logs get plain scrollback. Survivors, timeouts, and harness errors remain visible after the live display advances.

--verbose prints a line for every mutant and timing details for each phase. --quiet suppresses live progress entirely and takes precedence over --verbose.

Summary

Functions

Returns whether the reporter is using an animated ANSI status block.

Returns a specification to start this module under a supervisor.

Clears the current status block without stopping the reporter.

Returns whether persistent labels may use color.

Returns whether the default live reporter should draw its ANSI status block for the detected stderr terminal state.

Renders a verbose phase-completion event as a persistent status line.

Estimates remaining seconds from completed work, remaining work, and elapsed seconds.

Stops the status display and clears its terminal lines.

Milliseconds as one-decimal seconds (e.g. 450 → "0.5s", 3100 → "3.1s").

Seconds as Ns (under a minute) or Nm Ss.

Returns the persistent {label, color} for status, or nil when the status only updates the counter.

Renders the macro-expansion poison fallback as a loud, persistent warning: names the inline DSL macro(s) whose argument wouldn't compile with a mutation spliced in, and the copy-paste route to pin the skip up front — :raw for a call, :skip for a head Mutare analyzes structurally (Kernel.in), and a # mutare:ignore pointer for a head no route can name (the classification Mutare.Poison.Hint uses).

Records a phase transition or verbose detail event.

Renders one compile-poison recovery round as a persistent status line: how many mutants were dropped and how many block macros were escalated (skipped wholesale), naming the escalated macros so the line explains why the compile is being retried.

Records a completed mutant result and updates the progress display.

Updates scanning progress with the processed file count, total file count, and number of mutants found.

Renders the app-build seed's outcome (Mutare.Sandbox.Seed.summary/0) as a persistent status line, or nil for a :skipped seed (the broad-run default — no line even in verbose). :seeded shows the reused vs recompiling beam counts; :partial adds how many apps fell back (an umbrella per-app miss); :fallback names the otherwise-silent fall back to a cold compile.

Starts the live reporter.

Records the mutant currently being tested.

Returns the status-block lines for state at monotonic time now_ms.

Returns the persistent {label, color} for status in verbose mode.

Types

phase_event()

@type phase_event() ::
  :scanning
  | :compiling
  | :baseline
  | :coverage_probe
  | {:running, non_neg_integer()}
  | {:compiled, non_neg_integer()}
  | {:baseline_done, non_neg_integer()}
  | {:coverage_done, map()}
  | {:run_config, map()}
  | {:poison_round, map()}
  | {:macro_poison, map()}

Functions

animating?(server)

@spec animating?(GenServer.server()) :: boolean()

Returns whether the reporter is using an animated ANSI status block.

Plain reporters emit persistent lines but do not display the current-mutant activity line.

child_spec(init_arg)

Returns a specification to start this module under a supervisor.

See Supervisor.

clear(server)

@spec clear(GenServer.server()) :: :ok

Clears the current status block without stopping the reporter.

This call is synchronous, so the terminal is clear before subsequent output.

color_enabled?()

@spec color_enabled?() :: boolean()

Returns whether persistent labels may use color.

Any non-empty NO_COLOR value disables color. This does not disable ANSI cursor animation.

default_ansi?(stderr_tty?)

@spec default_ansi?(boolean()) :: boolean()

Returns whether the default live reporter should draw its ANSI status block for the detected stderr terminal state.

This is intentionally independent of IO.ANSI.enabled?/0: Elixir's flag is initialized from stdout, but Mutare's live UI is written to stderr.

detail_line(arg)

@spec detail_line(tuple()) :: String.t()

Renders a verbose phase-completion event as a persistent status line.

eta_secs(done, remaining, elapsed)

@spec eta_secs(non_neg_integer(), non_neg_integer(), non_neg_integer()) ::
  non_neg_integer() | nil

Estimates remaining seconds from completed work, remaining work, and elapsed seconds.

Returns nil until at least one item has completed and elapsed time is non-zero.

finish(server)

@spec finish(GenServer.server()) :: :ok

Stops the status display and clears its terminal lines.

humanize_ms(ms)

@spec humanize_ms(non_neg_integer()) :: String.t()

Milliseconds as one-decimal seconds (e.g. 450 → "0.5s", 3100 → "3.1s").

humanize_secs(s)

@spec humanize_secs(non_neg_integer()) :: String.t()

Seconds as Ns (under a minute) or Nm Ss.

leave_behind(status)

@spec leave_behind(Mutare.Result.status()) :: {String.t(), atom()} | nil

Returns the persistent {label, color} for status, or nil when the status only updates the counter.

macro_poison_line(map)

@spec macro_poison_line(map()) :: String.t()

Renders the macro-expansion poison fallback as a loud, persistent warning: names the inline DSL macro(s) whose argument wouldn't compile with a mutation spliced in, and the copy-paste route to pin the skip up front — :raw for a call, :skip for a head Mutare analyzes structurally (Kernel.in), and a # mutare:ignore pointer for a head no route can name (the classification Mutare.Poison.Hint uses).

phase(server, phase)

@spec phase(GenServer.server(), phase_event()) :: :ok

Records a phase transition or verbose detail event.

Phase transitions are :scanning, :compiling, :baseline, :coverage_probe, and {:running, total}. Detail events are {:compiled, ms}, {:baseline_done, ms}, {:coverage_done, summary}, and {:run_config, cfg}. {:poison_round, info} (a compile-poison recovery round) and {:macro_poison, info} (the macro-expansion fallback skipping an inline DSL macro) each leave a permanent line in every mode, not just verbose.

poison_round_line(map)

@spec poison_round_line(map()) :: String.t()

Renders one compile-poison recovery round as a persistent status line: how many mutants were dropped and how many block macros were escalated (skipped wholesale), naming the escalated macros so the line explains why the compile is being retried.

report(server, result)

@spec report(GenServer.server(), Mutare.Result.t()) :: :ok

Records a completed mutant result and updates the progress display.

scanned(server, progress)

@spec scanned(GenServer.server(), %{
  done: non_neg_integer(),
  total: non_neg_integer(),
  found: non_neg_integer()
}) :: :ok

Updates scanning progress with the processed file count, total file count, and number of mutants found.

Animated reporters redraw the status block. Plain reporters do not print a line for each update.

seed_line(map)

@spec seed_line(map()) :: String.t() | nil

Renders the app-build seed's outcome (Mutare.Sandbox.Seed.summary/0) as a persistent status line, or nil for a :skipped seed (the broad-run default — no line even in verbose). :seeded shows the reused vs recompiling beam counts; :partial adds how many apps fell back (an umbrella per-app miss); :fallback names the otherwise-silent fall back to a cold compile.

start_link(opts \\ [])

@spec start_link(keyword()) :: GenServer.on_start()

Starts the live reporter.

Options:

  • :device — output device; defaults to :standard_error
  • :ansi — enables or disables animation; by default it is enabled when stderr is a terminal
  • :color — enables or disables colored persistent labels; by default it follows animation and NO_COLOR
  • :width — terminal width; defaults to the detected width or 80
  • :verbose — retains a line for every mutant and shows phase details; defaults to false

started(server, site)

@spec started(GenServer.server(), Mutare.Site.t()) :: :ok

Records the mutant currently being tested.

status_block(state, now)

@spec status_block(map(), integer()) :: [String.t()]

Returns the status-block lines for state at monotonic time now_ms.

A running phase has an activity line and a counter. A pre-run phase has one line. An idle state has none. Cursor-control sequences are not included.

verbose_leave(status)

@spec verbose_leave(Mutare.Result.status()) :: {String.t(), atom()}

Returns the persistent {label, color} for status in verbose mode.

Every registered status has a verbose label. An unknown status raises.