Current section

Files

Jump to
mutare mix.exs
Raw

mix.exs

defmodule Mutare.MixProject do
use Mix.Project
@version "0.4.2"
@source_url "https://github.com/foxbenjaminfox/mutare"
# Internal modules deliberately kept `@moduledoc false`: the transform pipeline's
# stages (documented as one unit on `Mutare.Transform`) and a few plumbing modules.
# Public moduledocs still name them in prose — the references are worth keeping in
# source — so rather than delete the links we tell ExDoc not to autolink to them
# (they have no doc page), which also silences the "references X but it is hidden"
# warnings. Matched as a prefix, so a member/type/submodule reference
# (`Mutare.Mutator.Dispatch.mutations/3`, `Mutare.Transform.Uses.Harvest`) is covered
# by its parent's entry. Add a module here when a new `@moduledoc false` module gets
# referenced from a visible moduledoc (the warning tells you which). Also covers the
# rare case of a *stdlib* module that's hidden the same way (`Module.ParallelChecker`,
# named in `Mutare.Sandbox.CompilerOptions` — internal to the Elixir compiler, no doc
# page of its own).
@hidden_internal_modules ~w(
Module.ParallelChecker
Mutare.Transform.Resolve
Mutare.Transform.Uses
Mutare.Transform.Behaviours
Mutare.Transform.Analyze
Mutare.Transform.FunctionPlan
Mutare.Transform.ModulePlan
Mutare.Transform.Aliases
Mutare.Transform.Candidate
Mutare.Transform.SelectorEmit
Mutare.Transform.LiftedEmit
Mutare.Transform.CleanRegion
Mutare.Transform.CaseClauseEmit
Mutare.Transform.FnClauseEmit
Mutare.Transform.ReceiveClauseEmit
Mutare.Transform.RescueEmit
Mutare.Transform.ClauseVariants
Mutare.Transform.ImportWitness
Mutare.Transform.HostedEmit
Mutare.Transform.GuardBuild
Mutare.Transform.Tag
Mutare.Transform.ClauseAST
Mutare.Transform.BindingEscapeEmit
Mutare.Transform.Config
Mutare.Transform.ClauseGuardEmit
Mutare.Transform.CoverageEmit
Mutare.Transform.Invariants
Mutare.Runner.Hydrate
Mutare.Runner.Compile
Mutare.Runner.MutantRun
Mutare.Sandbox.Seed
Mutare.Sandbox.Mirror
Mutare.Options.Registry
Mutare.CallRouting.Registry
Mutare.Mutator.Dispatch
Mutare.Coverage.HelperTemplate
Mutare.Ignore.Directive
Mutare.Mutators.RegexLiteral.Tokens
)
# A couple of *typespecs* in visible modules reference a hidden internal type
# (`Mutare.Schema.t`'s `ineffective_ignores` field uses `Mutare.Ignore.Directive.t`;
# `Mutare.Calls`'s `module_key` re-exports `Mutare.Transform.Calls.module_key`;
# `Mutare.Options.t`'s `skip_lifting` field uses `Mutare.Lifting.skip_entry`;
# `Mutare.Sandbox.materialized`'s `seed` field uses `Mutare.Sandbox.Seed.summary`).
# Typespec autolinking bypasses `:skip_code_autolink_to`, so those are silenced by the
# *referencing* module instead — keep this list tight.
@typespec_refs_to_hidden ~w(Mutare.Calls Mutare.Options Mutare.Sandbox Mutare.Schema)
def project do
[
app: :mutare,
version: @version,
elixir: "~> 1.18",
description: description(),
package: package(),
elixirc_paths: elixirc_paths(Mix.env()),
start_permanent: Mix.env() == :prod,
deps: deps(),
dialyzer: dialyzer(),
docs: docs(),
aliases: aliases(),
# PropCheck.App starts `PropCheck.CounterStrike`, which opens a single
# on-disk DETS file (`_build/propcheck.ctex` by default) at *application
# boot* — i.e. on every `mix test` invocation, whether or not a
# `:property` test actually runs. Under `mix mutare --workers N` (N > 1),
# several `mix test` OS processes boot concurrently against the *same*
# sandboxed `_build`, and concurrent DETS opens/writes corrupt that file.
# Once corrupted it stays corrupted on disk, so — with `--keep-sandbox`
# — every subsequent run (any mutant, even a future invocation) fails
# `PropCheck.CounterStrike.init/1` and the *entire* baseline suite reports
# not-green, which mutare surfaces only as undifferentiated
# `:harness_error` on every mutant. Found dogfooding mutare on itself
# (`--workers 4`); see NOTES.md "PropCheck counter-examples DETS
# corruption under concurrent workers". Give each `mix test` process
# (identified by its own BEAM's OS pid) a private counter-examples file
# while under mutation (`MUTARE_ACTIVE_MUTANT` is set on the baseline,
# the coverage probe, and every mutant run — see `test/test_helper.exs`);
# normal local/CI runs keep the shared, cross-run-cached default file.
# Placed under the sandbox's own `_build` (every sandboxed `mix test` has
# its cwd set there — `Sandbox.Command.Invocation`), not the OS-wide tmp
# dir, so an ephemeral (non-`--keep-sandbox`) sandbox's teardown
# (`File.rm_rf(sandbox)`) removes it too instead of leaking one file per
# mutant run into `/tmp` forever.
propcheck: [
counter_examples:
if System.get_env("MUTARE_ACTIVE_MUTANT") do
Path.join(File.cwd!(), "_build/propcheck-#{System.pid()}.ctex")
end
]
]
end
def application do
[
extra_applications: [:logger]
]
end
defp elixirc_paths(:test), do: ["lib", "test/support"]
defp elixirc_paths(_), do: ["lib"]
defp description do
"A compile-once mutation testing tool for Elixir."
end
defp package do
[
maintainers: ["Benjamin Fox"],
licenses: ["MIT"],
links: %{
"GitHub" => @source_url,
"Changelog" => "https://hexdocs.pm/mutare/changelog.html"
},
# Hex's default file set already includes LICENSE*, README*, mix.exs,
# and lib/; list it explicitly so the bundled demo projects don't slip
# into the package while keeping the licence and docs in.
files: ["lib", "mix.exs", "README.md", "CHANGELOG.md", "LICENSE"]
]
end
defp deps do
[
{:sourceror, "~> 1.12.3"},
# Powers the `mix mutare.install` / `mix igniter.install mutare` generator
# (see `Mix.Tasks.Mutare.Install`). Optional so it isn't forced on projects
# that add Mutare by hand — the installer module is compiled away when absent
# (`Code.ensure_loaded?(Igniter)` guard) — while still being fetched here so
# the task compiles and is tested. A consumer who runs `mix igniter.install`
# already has it.
{:igniter, "~> 0.8", optional: true},
{:propcheck, "~> 1.5", only: [:dev, :test]},
{:credo, "~> 1.7", only: [:dev, :test], runtime: false},
{:dialyxir, "~> 1.4", only: [:dev], runtime: false},
{:ex_doc, "~> 0.34", only: :dev, runtime: false}
]
end
defp docs do
[
main: "readme",
source_url: @source_url,
source_ref: "v#{@version}",
extras: ["README.md", "guides/extending.md", "CHANGELOG.md", "LICENSE"],
skip_code_autolink_to: &skip_autolink_to?/1,
skip_undefined_reference_warnings_on: &(&1 in @typespec_refs_to_hidden),
# Modules fall into the first group whose entry matches, so the explicit
# lists win over the trailing catch-alls. "Internal" (`~r//`) sweeps up
# everything else — the transform pipeline, sandbox, coverage plumbing, etc.
# `@moduledoc false` modules never appear at all.
groups_for_modules: [
"Core API": [
Mutare,
Mix.Tasks.Mutare,
Mutare.Ignore,
Mutare.Run,
Mutare.Runner,
Mutare.Options,
Mutare.Result,
Mutare.Score,
Mutare.Site
],
"Writing mutators": [
Mutare.Mutator,
Mutare.Mutator.Structural,
Mutare.Mutator.MacroHost,
Mutare.Mutator.Mutation,
Mutare.Mutator.Spec,
Mutare.AST,
Mutare.Test,
Mutare.Test.RoutingExtension,
Mutare.Calls,
Mutare.Analyze
],
"Writing extensions": [
Mutare.Extension,
Mutare.Extension.Spec,
Mutare.CallRouting,
Mutare.CallRouting.Registry,
Mutare.CallRouting.Spec,
Mutare.UseExpansion,
Mutare.UseExpansion.Expansion,
Mutare.UseExpansion.ContractError
],
"Built-in mutators": ~r/^Mutare\.Mutators/,
Reporters: [
Mutare.Report,
Mutare.Report.Json,
Mutare.Report.Html,
Mutare.Report.Sarif,
Mutare.Report.Live,
Mutare.Report.Live.Lines
],
Internal: ~r//
]
]
end
# True when `ref` (a prose autolink target) names one of the deliberately-hidden
# internal modules — the module itself, or any member/type/submodule under it.
defp skip_autolink_to?(ref) do
bare = ref |> String.replace_prefix("t:", "") |> String.replace_prefix("c:", "")
Enum.any?(@hidden_internal_modules, fn mod ->
bare == mod or String.starts_with?(bare, mod <> ".")
end)
end
defp dialyzer do
[
# Keep the PLTs in a stable, cacheable location (e.g. for CI).
plt_local_path: "priv/plts",
plt_core_path: "priv/plts",
plt_add_apps: [:mix, :ex_unit],
# High-signal spec-accuracy checks on top of the defaults. `:unmatched_returns`
# is intentionally left off: it fights idiomatic fire-and-forget side-effect
# calls (File.rm/1, etc.) with no genuine bugs to show for it here.
flags: [:error_handling, :extra_return, :missing_return]
]
end
defp aliases do
[
check: ["format --check-formatted", "credo", "dialyzer"]
]
end
end