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
source wrote doesn't matter.
Standalone/pipe clause-macro mutations — the composable cousins 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 cousin of Mutare.Ecto.Query's
whole-from clause drop, and the query-side twin of Mutare.Ecto.Changeset's validator drop.
Where Query removes a where:/limit: 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.
The routing classifier half of the selector host (Mutare.Ecto.Host): the
Mutare.CallRouting.route_arguments/2 callback that decides, per visible argument of a
:routing-registered query macro, how core should treat that position — :hosted (the plugin's
host weaves it), :expression (mutate it normally), :raw (leave it raw), :interpolated
(core mutates a scalar value, delivered through ^ interpolation), or {:keyword, …} (per-pair
shorthand routing). Mutare.Ecto.Host then consumes the :hosted decision to build and weave
the ^/dynamic target.
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 ! twins. 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)