Modules
A mutation-testing plugin for Ecto — a Mutare.Mutator that mutates the Ecto
surface (Repo calls, changeset pipelines, and the query DSL) while respecting
SQL semantics.
Positional binding-reorder mutants for every standalone/pipe query macro that takes a binding
pattern list — the where/having condition macros, select, select_merge, order_by,
group_by, distinct, join, preload, windows, …, and the free-standing dynamic/2
(the condition/clause/join/dynamic descriptors in Mutare.Ecto.Surface).
Drop a transparent step from a changeset pipeline — the changeset equivalent of removing a
guard. cs |> validate_required([:name]) → cs. A surviving mutant means no test
exercises the behaviour that step contributes. Matched by resolving the call to
Ecto.Changeset (direct, aliased, or the common import Ecto.Changeset), so the form the
call uses doesn't matter.
The per-argument routing classifier for the Ecto.Changeset pipeline stages the plugin mutates
(Mutare.Ecto.Changeset.stages/0 — every validator, constraint, and Repo-time hook it can
drop) — the changeset counterpart of the query classifier Mutare.Ecto.Host.Routing. Nothing
here is hosted; the classifier only holds back from core's families the positions where a
core swap would be a crash, not a mutant, so a run that lists the plugin next to :all
reports no crasher in a changeset stage. The test of each pin is Ecto's own handling of the
swapped value
Standalone/pipe clause-macro mutations — the composable counterparts of the whole-from family
in Mutare.Ecto.Query. Where Query mutates from's keyword clauses, this mutates the
same kinds of thing written as standalone calls or pipe stages
Drop a standalone/pipe query clause — the composable counterpart of Mutare.Ecto.Query's
whole-from clause drops, analogous to Mutare.Ecto.Changeset's validator drop.
Where Query removes a where:/limit:/group_by: clause from a from(…) keyword list, this
removes the same kinds of clause written as a standalone call or pipe stage
In-fragment SQL mutations for a free-standing dynamic/1,2 call — the condition a user
builds ahead of time and splices later
The plugin's own SQL-semantics mutation catalog for a query fragment — the boolean
condition of a where/having clause or of a free-standing dynamic/1,2. Given a condition's
AST, mutants/1 returns every single-point variant: the same condition with exactly one
operator/predicate swapped, one variant per mutatable position. Each variant is a full,
compile-safe alternative the SQL engine will actually run; the host (Mutare.Ecto.Host) weaves
them behind a ^/dynamic selector so one of them bakes into the query per run, while
Mutare.Ecto.Dynamic rebuilds a free-standing dynamic call whole (an ordinary expression
position, needing no weave).
Builds selector-host targets for localized Ecto query conditions.
Classifies arguments of registered query macros for Mutare.CallRouting.route_arguments/1.
Core follows these routes; Mutare.Ecto.Host supplies the targets at :hosted positions.
Whole-from query mutations — the ones expressible without Mutare's foreign-semantics
DSL host. Each returns a whole mutated from(...) node, which
Mutare's ordinary in-place selector wraps; the localized in-fragment mutations (operator
swaps inside a where, via ^/dynamic) arrive with the host extensions.
Swap Ecto.Query.first/1,2 ↔ Ecto.Query.last/1,2 — the query "terminals" that restrict a
query to a single edge of its ordering. first keeps the first row by the order (primary key
ascending when none is given); last reverses it. Swapping them is a clean, same-arity rename
(so it always compiles), behaviour-changing whenever the query can return more than one row — a
survivor means no test pins which end the query is taking.
Swap the aggregate of a Repo.aggregate/3 call along an SQL-meaningful ladder —
:sum↔:avg, :min↔:max. A surviving mutant means no test distinguishes, say, the
sum of a column from its average: the aggregate is computed but its kind is unchecked.
Mutations on the persisting Repo writes — insert/update/delete/insert_or_update
and their ! variants. Two families, both matched by resolving the call's module to one of the
configured repo: modules (so direct, aliased, and use Ecto.Repo-defined forms all match)
The condition the host cannot weave, and its whole-call delivery.
Swap a validate_number/3 bound between its strict and non-strict form — the changeset
counterpart of the in-query :comparison swap. validate_number(:age, greater_than: 0) →
validate_number(:age, greater_than_or_equal_to: 0). "Is the bound itself tested?" — the two
validations accept the same values except one exactly on the bound, so the mutant survives
unless a test builds a changeset whose value equals it and asserts the validation result. Family
:validation_boundary; equivalence-sensitive (Mutare.Ecto.Equivalence), because a kill needs
exactly that fixture.