Muex.Sandbox (Muex v0.13.1)

View Source

Creates isolated working directories for parallel mutation testing.

Each sandbox mirrors the project structure using symlinks, with its own _build directory and a copy of the single mutated source file. This allows multiple mix test processes to run simultaneously without seeing each other's mutations.

Supports both standard Mix projects and umbrella projects.

Structure (umbrella)

worker_N/
├── <siblings of the project dir>  → symlink (all but .git)
└── <project dir name>/
    ├── mix.exs, config/, deps/, ...  → symlink to project
    │                   (every top-level entry except apps/ and _build/)
    ├── apps/          → copy-on-write clone of the project's apps/
    │   └── my_app/lib/mutated.ex → overwritten with the mutated source
    └── _build/<env>/  → copy-on-write clone, compiled once when the
                         pool is created (see create_pool/2)

Summary

Functions

Applies a mutation to a sandbox by writing the mutated source to the sandbox's copy of the file, and deleting the stale beam so the child mix test process recompiles it.

Raises Muex.Sandbox.Error if any file cannot be mutated safely.

Cleans up all sandbox directories.

Creates a pool of reusable sandbox directories.

Creates a single sandbox directory mirroring the project.

Restores a sandbox after a mutation by copying the original source file back over the mutated copy.

Types

sandbox()

@type sandbox() :: %{
  :root => Path.t(),
  :project_root => Path.t(),
  :build_env => String.t(),
  optional(:base) => Path.t(),
  optional(:mirror) => [String.t()]
}

Functions

apply_mutation(sandbox, original_path, mutated_source, module_name)

@spec apply_mutation(sandbox(), Path.t(), String.t(), atom() | nil) ::
  {:ok, boolean()} | {:error, term()}

Applies a mutation to a sandbox by writing the mutated source to the sandbox's copy of the file, and deleting the stale beam so the child mix test process recompiles it.

Returns {:ok, false} on success (the child always recompiles), or {:error, reason}.

check_targets!(project_root, files)

@spec check_targets!(Path.t(), [Path.t()]) :: :ok

Raises Muex.Sandbox.Error if any file cannot be mutated safely.

In an umbrella sandbox only apps/ is a private copy; every other top-level entry is a link to the real project. A mutant of lib/foo.ex or config/x.exs would be written through that link into the real file, and restore/2 would then delete it. Such targets are refused before any sandbox exists. Outside an umbrella this always returns :ok.

cleanup(sandboxes)

@spec cleanup([sandbox()]) :: :ok

Cleans up all sandbox directories.

create_pool(count, opts \\ [])

@spec create_pool(
  non_neg_integer(),
  keyword()
) :: [sandbox()]

Creates a pool of reusable sandbox directories.

Returns a list of sandbox structs that can be checked out by workers.

create_sandbox(root, project_root, build_env, test_paths, mirror \\ [])

@spec create_sandbox(Path.t(), Path.t(), String.t(), [String.t()], [String.t()]) ::
  sandbox()

Creates a single sandbox directory mirroring the project.

restore(sandbox, original_path)

@spec restore(sandbox(), Path.t()) :: :ok

Restores a sandbox after a mutation by copying the original source file back over the mutated copy.