Skip to content

moonrunnerkc/nondet

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

21 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

nondet

build   license: MIT   Java 21

Find the reads that make a JVM program flaky, and get a deterministic repro.

Install · Quick start · Sources · Usage · Examples · How it works · Evidence · Limits · Changelog


Some programs pass on your machine and fail on someone else's, or fail one run in ten. The cause is almost always a hidden read of something that changes between runs: the clock, a random number, an environment variable, a system property. nondet finds the read that actually controls the failure and hands back a repro that reproduces it.

It works two ways:

  • scan reads your compiled classes and lists every place the code touches one of these sources. It loads nothing and runs nothing.
  • check runs your program a few times and compares what it produced, not just what it read. When the runs differ, --minimize narrows the differing reads to the minimal set that controls the outcome and writes a repro bundle plus a nondet replay command that reproduces the failure. A read whose value varies but never changes the result is not flagged.

Personal open source under github.com/moonrunnerkc. Not an Aftermath Technologies product.

Install

You need Java 21 and Maven.

mvn -DskipTests package

That produces two jars: the command (cli/target/nondet-cli.jar) and the agent (agent/target/nondet-agent.jar). Run the command with the wrapper or plain java:

./nondet --help
java -jar cli/target/nondet-cli.jar --help

Quick start

Scan the bundled examples to see what they read:

$ ./nondet scan examples/target/classes
nondet scan: 7 entropy call sites

TIME (4)
  io.github.moonrunnerkc.nondet.examples.FlakyRetry.attempts  line 39  System.nanoTime
  io.github.moonrunnerkc.nondet.examples.FlakyRetry.attempts  line 41  System.nanoTime
  io.github.moonrunnerkc.nondet.examples.samples.RetryWithTimeout.attempts  line 43  System.currentTimeMillis
  io.github.moonrunnerkc.nondet.examples.samples.RetryWithTimeout.attempts  line 45  System.currentTimeMillis

RANDOM (2)
  io.github.moonrunnerkc.nondet.examples.HashOrder.firstKey  line 39  UUID.randomUUID
  io.github.moonrunnerkc.nondet.examples.samples.RandomShardRouter.main  line 28  Math.random

SYSPROP (1)
  io.github.moonrunnerkc.nondet.examples.samples.ConfigGreeting.main  line 30  System.getProperty

Now pin the cause of one that is not reproducible, and get a repro:

$ ./nondet check --minimize --class-path examples/target/classes \
    io.github.moonrunnerkc.nondet.examples.HashOrder
nondet check: the runs produced 2 different outcomes

  outcome A  fingerprint 6c8416bfb065  exit 0  run 1
  outcome B  fingerprint 2638869a0512  exit 0  run 2

nondet check: minimal causal set: 1 read controls the outcome

  io.github.moonrunnerkc.nondet.examples.HashOrder.firstKey line 39 (UUID.randomUUID)  [read #3 at this site]

repro bundle: nondet-repro.bundle
reproduce with:
  nondet replay --bundle nondet-repro.bundle --class-path examples/target/classes \
    --expect 2638869a0512... io.github.moonrunnerkc.nondet.examples.HashOrder

The replay command serves the recorded UUID back and exits 0 only if it reproduces outcome B, so it verifies itself. Exit code 1 means the outcomes differed; 0 means they agreed, which is the honest no-false-positive case, for example FlakyRetry, whose clock varies but whose result does not. More runs, including a real cause pinned inside third-party code, are in Examples and the evidence report.

Sources

The six entropy sources nondet knows, frozen through v0.2.0:

Source Kind
System.nanoTime TIME
System.currentTimeMillis TIME
Math.random RANDOM
UUID.randomUUID RANDOM
System.getenv ENV
System.getProperty SYSPROP

A read reached through Method.invoke is covered and attributed to its caller; one reached through a MethodHandle, or a cause outside these six sources, is reported as an unattributed divergence rather than pinned. An outcome-stable result is not a proof of determinism. See Limits for the full picture, with evidence.

Documentation

Guide What's in it
Usage Every flag, its default, and what each exit code means.
Examples Real captured runs, including the case where nothing diverges and why.
How it works The catalog, outcomes, bundles, replay, and the causal search, in plain terms.
Evidence How to run the corpus campaign and turn a repro bundle into a fix.
Limits and roadmap What nondet cannot catch yet, with evidence, and what is planned next.
Changelog Release notes.

License

MIT. See LICENSE.