Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Coverage

rafiki coverage measures function-level test coverage for Monkey C by rewriting the source before compilation — Connect IQ has no native coverage support to hook into.

Note

Coverage is function-level only. It answers “which functions never ran under the test suite”, not which lines or branches did — a fully executed 200-line function counts the same as a one-line one.

Usage

test runs the whole pipeline — instrument, compile, run under the simulator, and report — in one step, using the paths instrument already decided instead of asking you to retype them:

rafiki coverage test -d <device> -y key

That needs monkeyc and monkeydo on PATH, and the simulator already running. monkeyc’s and monkeydo’s own output stays out of the way on a clean run — test prints the instrument summary, monkeydo’s own one-line verdict (PASSED (passed=1, failed=0, errors=0)), and the coverage table, nothing more (COVHIT lines are always stripped from anything printed, whichever branch below it takes). The full raw output only shows up once something looks wrong, and what happens next depends on what “wrong” means:

  • A non-zero monkeyc exit, or a monkeydo run that produced no coverage hits at all (even after a --start-simulator retry): test stops right there and exits non-zero. No coverage table — the build never ran, so a “0/N covered” table would misrepresent that as a real (if terrible) result rather than a broken pipeline.
  • A monkeydo run whose verdict line reports a failed or errored test (FAILED (passed=0, failed=0, errors=1)): test still exits non-zero, but the coverage table is still printed afterwards, since the code did run and that data is still meaningful.

None of this reads monkeydo’s own exit status — it isn’t a reliable pass/fail signal on its own (a run with a real failing test can still exit zero, and a fully passing one can still exit non-zero) — test reads the same verdict line and coverage hits the human-readable output already shows.

Add --start-simulator to have it launch the simulator itself (via connectiq, installed alongside monkeyc/monkeydo) if the first monkeydo attempt comes back with no coverage hits, and retry once:

rafiki coverage test -d <device> -y key --start-simulator

--start-simulator is opt-in rather than default because connectiq brings an already-running simulator’s window to the front, which is only wanted when monkeydo actually needed it. Pass --dry-run to print the monkeyc/monkeydo commands test would run without running them.

Running the steps by hand

test is a convenience wrapper; each step also works standalone, e.g. for CI or to debug one stage at a time:

rafiki coverage instrument
monkeyc -f bin/coverage/coverage.jungle -d <device> -o bin/coverage/cov.prg -y key --unit-test
monkeydo bin/coverage/cov.prg <device> -t | rafiki coverage report -

Note

The simulator needs to be running when executing monkeydo.

To keep the raw simulator output around, capture it to a file instead of piping it straight into report:

monkeydo bin/coverage/cov.prg <device> -t | tee bin/coverage/run.log
rafiki coverage report bin/coverage/run.log

How it works

  1. instrument parses every source file, splices a probe (AutoGeneratedCov.hit(N)) after each function’s opening brace, and writes the rewritten sources — byte-identical apart from the probes — plus a generated AutoGeneratedCov.mc runtime and a coverage-manifest.tsv into the output directory. It also copies the project’s jungle file to coverage.jungle, rewritten to build from there (see below).
  2. Compiling that output directory with monkeyc --unit-test and running it with monkeydo -t prints one COVHIT <id> line the first time each probe executes.
  3. report joins that log against the manifest and prints per-file coverage, listing every function that never ran.

Declarations annotated with (:test) or (:release) are always skipped — test code doesn’t get to count itself, and unit tests run in debug mode regardless of (:release). --exclude-annotation adds more names to skip, e.g. ones a jungle sets via excludeAnnotations.

Everything anchors to the project root — the nearest ancestor holding manifest.xml — so instrument can run from any subdirectory of the project and still cover the whole thing, and so files that share a name in different directories never collide in the output.

The generated coverage.jungle

The copied jungle needs two edits to build correctly from its new home in the output directory:

  • project.manifest is repointed at the real manifest.xml, since it isn’t mirrored into the output directory the way .mc sources are.
  • Every sourcePath gains a . entry, if it doesn’t already have one, so monkeyc also picks up AutoGeneratedCov.mc, which sits directly in the output directory rather than mirrored under it.

resourcePath entries are copied as-is and not rebased, so a project with resources currently needs to fix those up by hand.

Flags

instrument

FlagDefaultMeaning
[FILES]...whole projectFiles or directories to instrument.
--out{root}/bin/coverageOutput directory; cleared on every run.
--jungle{root}/monkey.jungleJungle file to copy into coverage.jungle.
--exclude-annotationnoneExtra annotations to skip, comma-separated or repeated. test/release are always skipped.

report

ArgumentDefaultMeaning
<LOG>requiredCaptured simulator log; - reads it from stdin.
--dir{root}/bin/coverageDirectory holding coverage-manifest.tsv.
--out-formattexttext for a human-readable table, or lcov for an LCOV .info file (genhtml, VS Code Coverage Gutters, Codecov, Coveralls).
--outstdoutWrite the report here instead of stdout.

The simulator may reinitialize module state between unit tests, so probe ids can repeat in the log; report deduplicates while joining. Only lines that are exactly COVHIT <id> are counted — anything else the simulator interleaves is ignored.

test

Takes instrument’s [FILES]..., --exclude-annotation and --jungle flags, plus report’s --out-format/--out for the report it produces at the end. --out on instrument and --instrument-out here both mean the instrumentation output directory — they’re just named differently since test also needs --out for the report path.

FlagDefaultMeaning
-d, --devicerequiredDevice to build and run for, e.g. fr965. Passed to monkeyc -d and monkeydo.
-y, --keyrequiredDeveloper key. Passed to monkeyc -y.
--instrument-out{root}/bin/coverageInstrumentation output directory (same as instrument’s --out).
--out-formattexttext for a human-readable table, or lcov for an LCOV .info file.
--outstdoutWrite the coverage report here instead of stdout.
--start-simulatoroffLaunch connectiq and retry once if the first monkeydo attempt has no hits.
--simulator-boot-time5Seconds to wait after launching the simulator before retrying.
--dry-runoffPrint the monkeyc/monkeydo commands instead of running them.
-- <MONKEYC_ARGS>...noneExtra arguments forwarded to monkeyc verbatim, e.g. -- -O 3 -w.