Resolve the shape of the target into a copy-root and a mutate-scope.
Mutare copies one tree into the sandbox and mutates a set of source files in it.
For a plain Mix project those are the same directory. For an umbrella they
differ: the whole umbrella is copied (so in_umbrella sibling deps and the
shared deps//config/ keep resolving), while only a chosen subset of apps/*
gets metamutants.
resolve/2 turns the user-supplied target path plus the --app/--workspace
flags into:
copy_root— the directory materialised into the sandbox (the umbrella root, or the project root for a single app). This is therootthe rest of the pipeline (Mutare.Schema,Mutare.Sandbox,Mutare.Runner) treats as authoritative for every path operation; the struct duplicates it only so the entry points know what to pass.mutate_scope— the apps whose sources are mutated, as%{app, dir}entries withdirrelative tocopy_root("apps/foo", or"."for a single app).apps— every app whose suite runs (all umbrella apps), which the per-app bootstrap injection and the dependency-graph scoping need.
Detection is static — Mutare runs outside the target's Mix, so the layout is
read from the filesystem (an apps/ dir plus an apps_path: in the root
mix.exs) rather than from Mix.Project.
Summary
Functions
Per mutate-scope app, the root-relative test/ dirs a broad (whole-suite) run
may be narrowed to: the app itself plus every app that (transitively) depends on
it. A mutant in app A can only be killed by a test that executes A's code, and a
sibling executes A's code only through a declared dependency — so this set is a
safe superset of A's possible killers; narrowing below it would risk a false
survivor, so we never do.
Resolve target (+ scope flags) into a t/0.
Is dir (an absolute path) the root of an umbrella project?
Types
Functions
@spec app_test_scopes(t(), Path.t(), %{required(atom()) => [atom()]}) :: %{ required(atom()) => [String.t()] }
Per mutate-scope app, the root-relative test/ dirs a broad (whole-suite) run
may be narrowed to: the app itself plus every app that (transitively) depends on
it. A mutant in app A can only be killed by a test that executes A's code, and a
sibling executes A's code only through a declared dependency — so this set is a
safe superset of A's possible killers; narrowing below it would risk a false
survivor, so we never do.
forward is the declared inter-app graph, %{app => [apps it depends on]},
as Mutare.Runner.AppGraph.read/2 reads it from Mix (nodes and deps outside the
umbrella are ignored). Declared, not runtime: a runtime: false sibling dep is
absent from the compiled .app's applications yet fully callable from the
dependent's tests. Only dirs that actually exist under sandbox are returned.
Returns %{} (⇒ no narrowing, run the whole umbrella) for a single app, or when
forward lacks an umbrella app — a missing node would hide that app's
dependents, so degrade safe rather than narrow on doubt.
Resolve target (+ scope flags) into a t/0.
opts:
:apps— app-name strings to mutate (from--app);nil/[]means "all".:workspace—truemutates every app (from--workspace).
A single (non-umbrella) project resolves to copy_root: target with a lone
%{app: nil, dir: "."} scope, so the existing single-app pipeline is unchanged.
Is dir (an absolute path) the root of an umbrella project?