Source-line code coverage for TON smart contracts. Works with @ton/sandbox + Jest test suites. Emits standard LCOV consumable by:
- VSCode — Coverage Gutters extension (green/red markers in the gutter)
- JetBrains IDEs — native
Run > Show Coverage Data(IntelliJ, WebStorm, etc.) - Codecov / Coveralls — drop
lcov.infointo CI, get PR coverage-diff comments genhtml— standalone HTML report
Plus a built-in self-contained HTML report with syntax highlighting, gutter bars, multi-line throw classification, and a conditional-throws branch-coverage column.
Functional for FunC. Tolk support is planned once the Tolk compiler exposes structured debug-info (it currently only emits Fift line-comments). Tact is out of scope — different coverage story, different tool.
| Language | Status | Notes |
|---|---|---|
| FunC | working | Uses @ton-community/func-js debugInfo |
| Tolk | planned | Blocked on structured debug-info from tolk-js |
| Tact | out of scope | Separate tool |
npm install --save-dev tonofcovPeer-deps: @ton/sandbox ≥ 0.37, @ton/core ≥ 0.63, @ton/blueprint ≥ 0.41, @ton-community/func-js ≥ 0.10, jest ≥ 29.
Known FunC compiler crash — pin
func-js-binThe
@ton-community/func-js-bin0.4.6-wasmfix.debugger.1WASM build hard-crashes on some FunC constructs withRuntimeError: null function or function signature mismatchduring debug-info compilation. The exact trigger isn't yet isolated but it reproduces on larger contracts (e.g. a full jetton-minter implementation).tonofcov's compile hook catches the crash and gracefully falls back to a non-debug compile for that specific contract — so your tests still run, but coverage data for it is dropped (it won't appear in the report).
To get coverage for crashing contracts, pin to the last known-good build via npm
overrides:"overrides": { "@ton-community/func-js-bin": "0.4.6-wasmfix.debugger.0" }Isolating the minimal FunC reproducer is a pending investigation — if you encounter the crash, a minimal
.fc+ stack trace in an issue is very welcome.
Extend your Jest config to use the preset:
// jest.config.js
module.exports = {
preset: 'tonofcov/jest-preset',
// ...your existing config
};Run tests as usual:
npm testAfter the suite finishes, tonofcov writes:
coverage/lcov.info— for IDE / CI toolingcoverage/html/index.html— standalone HTML report (on by default)coverage/gaps.md+coverage/gaps.json— agent-friendly listing of uncovered functions, partial throws, and uncovered ranges (on by default)
Every FunC source line is classified covered / uncovered / partial / non-exec. inline and inline_ref functions propagate hits back to their call sites, and multi-line statements are kept consistent.
Each throw_if / throw_unless / throw_arg_if / throw_arg_unless is a two-sided branch: the happy path (cond matched → no throw) and the error path (cond matched → threw). Lines where only one side fired are marked partial (yellow). The HTML report shows per-throw fire counts in a dedicated column, and the index page has a separate "Conditional throws" progress section.
coverage/gaps.md and coverage/gaps.json list everything uncovered, grouped by file and prioritized by actionability:
- UNCOVERED_FN — a function with zero interior hits. Biggest single-test win.
- PARTIAL_THROW — a conditional throw whose error branch never fired (or always fired). Untested error paths.
- UNCOVERED_RANGE — contiguous uncovered lines inside an otherwise-covered function. Usually a missed branch.
Each entry includes the source snippet plus mechanical context — enclosing function, nearest conditional header, call sites. No natural-language hints; the consuming agent reads the code and decides how to trigger each gap.
Per-file page: [throws counter | hit counter | line # | coloured border | syntax-highlighted source]. Index page: Lines block (total / hit / % / bar) and Conditional-throws block (total / fired / % / bar). Files matching the stdlib exclude default are pushed below a divider and don't affect overall percentages.
All configuration is via environment variables.
| Variable | Default | Effect |
|---|---|---|
TONOFCOV_HTML |
enabled | Set to 0 / false / off to skip HTML report generation. |
TONOFCOV_NO_HTML |
— | Alternative kill switch for CI. =1 disables HTML. |
TONOFCOV_GAPS |
enabled | Set to 0 / false / off to skip the gaps report (gaps.md + gaps.json). |
TONOFCOV_NO_GAPS |
— | Alternative kill switch for gaps. =1 disables. |
TONOFCOV_OUT_DIR |
coverage |
Output directory, relative to cwd. |
TONOFCOV_INCLUDE |
— | Comma-separated globs. If set, ONLY matching files contribute to overall totals. |
TONOFCOV_EXCLUDE |
**/stdlib.fc |
Comma-separated globs. Matching files are shown but don't count toward totals. Set to empty string to count everything. |
TONOFCOV_TEST_NAME |
"" |
Populates LCOV's TN: field. |
TONOFCOV_NO_INLINE_PROPAGATE |
— | Set to 1 to skip inline-propagation and post-processing passes; raw aggregation only. |
Globs support * (any char except /) and ** (any chars including /).
name: ci
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: npm
- run: npm ci
- run: npm test
env:
TONOFCOV_NO_HTML: "1" # LCOV only in CI; skip HTML
- uses: codecov/codecov-action@v4
with:
files: coverage/lcov.info
fail_ci_if_error: trueFor GitLab CI, Bitbucket Pipelines, etc. — the pattern is identical: run Jest, upload coverage/lcov.info.
Binary coverage (line shown as covered vs not) is reliable — that's what LCOV is for and what we do well.
Hit counts are approximate. Relative ordering (A runs more than B) within a function is usually meaningful, but absolute numbers can be off by a small factor because the FunC compiler emits multiple opcodes per source statement. Conditional headers (if / while / etc.) are normalized; straight-line statements are not. See KNOWN_ARTIFACTS.md for the full catalogue of compiler-level quirks.
Gas counters are tracked internally but not yet exposed — the values double-count across inline expansions and are inconsistent with normalization. See artifact #8.
- A compile hook replaces blueprint's
doCompileFuncto forcedebugInfo: true, then parses the debug-marks Cell into(cell_hash, offset) → location_keysmaps. - A sandbox hook wraps
Executor.runTransaction/runTickTock/runGetMethodto bump vmLog verbosity tofull_location_stackand capture every executed step's cell hash, offset, gas, and exception state. - After tests (
afterAllin the worker, thenglobalTeardownin the parent process), vmLogs are aggregated against the compile cache → raw per-line hits and throw counts. - Post-processing: inline-propagation, multi-line-statement propagation, sequential-fill, dead-branch suppression, conditional-header normalization, return-statement capping, non-code-hit stripping, function-signature propagation.
- Emit LCOV and (by default) a static HTML report.
MIT.
Built on top of work from @ton/sandbox's debug-marks format, @ton-community/func-js structured debug info, and @scaleton/tree-sitter-func (GPL-3.0 grammar, loaded at runtime). This package itself is MIT.