eta

Deterministic simulation testing for the BEAM.

(Said like the Greek letter η, AY-tuh or EE-tuh.)

When your project's tests run, eta transforms your code via an Erlang parse_transform so that it can be driven by a special deterministic scheduler and a virtual clock. You define a meaningful workload to exercise your system, and eta will help you inject faults to reach interesting behavior. When pathology is found, eta gives you a perfectly reproducible trace to debug. This all happens at test time; the code that gets deployed to prod is untouched.

What it does

Getting Started

A system under test provides some strictly deterministic callbacks (see eta_harness), and a run is one call, with a seed. In this example the seed is 7:

#{outcome := Outcome, trace := Trace} =
eta_run:run(my_harness, #{seed => 7, max_ops => 25, max_steps => 20000, preload => [my_app]}).

When a run fails, shrink it:

#{trace := Minimal, verified := true} =
eta_shrink:shrink(my_harness, Trace, #{seed => 7, max_ops => 25, preload => [my_app]}).

The seed produces the same trace each time, and the shrink reduces the size of that trace.

eta includes some simple distributed systems in order to illustrate DST:

  1. A two-phase commit implementation, which is used in eta's own tests.
  2. An ABD Qurom Register, that is written as an end-to-end walkthrough.
  3. A leader election by heartbeat lease, for which correctness relies heavily on the clock, unlike the first two examples.

Documentation

  1. What DST is. Discusses the kinds of bugs we're looking for, how the technique works, and how your project implementation needs to change to use it.
  2. Setting up a project. Includes build configuration, some first steps to remove nondeterminism, and getting the system running for the first time.
  3. A worked example. A simple two-phase commit implementation, with an included bug for demonstration.
  4. Writing a system under test. The behaviour callbacks and the practices around them.
  5. Gotchas and footguns. Nondeterminism can leak in from many sources. This page details some common pain points.
  6. A journey through DST. The whole thing end to end: an empty directory becomes a quorum register with a planted bug, and the bug becomes a minimized, replayable failure that explains itself.

docs/design.md is still a work in progress. Eventually, it will be the internal specification for the eta project itself.

Installation

We suggest setting up a separate build profile called dst.

def deps do
[{:eta, "~> 0.1", only: :dst}]
end

and in rebar3, a dst profile can run as rebar3 as dst eunit.

Your modules don't include eta.hrl directly. They include a wrapper of your own, include/myapp_eta.hrl, which is what keeps eta out of every other build:

-ifdef(DST).
-include_lib("eta/include/eta.hrl").
-else.
-define(ETA_LABEL(Label), ok).
-define(ETA_LOG(Event), 0).
-endif.

Setting up a project has the whole configuration for both build tools, and the reasons for the design choices.

Status

Early and under development.

Known gaps, roughly by size

AI full disclosure

Etymology

The name is short for étalon. You may also think of it as the Erlang Trace Augur.


The eta in the logo is outlined from STIX General Italic, used under the SIL Open Font License.