Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions .github/scripts/stamp-coverage-chapter.awk
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Replace the block between COVERAGE_STATUS_BEGIN/END markers in the book's
# code-coverage chapter with a fresh status line. Driven by `make coverage`.
# Required vars: commit, ts, covered, link.

/<!-- COVERAGE_STATUS_BEGIN -->/ {
print "<!-- COVERAGE_STATUS_BEGIN -->"
print "**Last collected:** " ts " for commit `" commit "` — **" covered " line coverage**."
print ""
print "[Open the report](" link ")"
in_block = 1
stamped = 1
next
}

/<!-- COVERAGE_STATUS_END -->/ {
print "<!-- COVERAGE_STATUS_END -->"
in_block = 0
next
}

!in_block { print }

# A lone BEGIN marker would silently swallow the rest of the chapter, and a
# chapter without markers would pass through unstamped; fail loudly instead.
END {
if (in_block || !stamped) {
print "error: COVERAGE_STATUS_BEGIN/END marker pair not found" > "/dev/stderr"
exit 1
}
}
10 changes: 10 additions & 0 deletions .github/workflows/book.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,16 @@ jobs:
- name: Install and test the mdBook
run: make test-book

- name: Verify coverage chapter is in default state
# Absence of the awk-generated stamp marker. Decouples CI from the
# exact default-block wording so the latter can be reworded freely.
run: |
if grep -q "^\*\*Last collected:\*\*" \
book/src/developer_guide/coverage.md; then
echo "::error::book/src/developer_guide/coverage.md is stamped with local coverage; revert it before committing."
exit 1
fi

- name: Build book
run: mdbook build book

Expand Down
164 changes: 164 additions & 0 deletions .github/workflows/code-coverage.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
# Label-gated coverage workflow (`measure code coverage`). Uses
# `pull_request_target` so the run holds a write token for publishing to
# gh-pages — which means it must never leak that token to the PR code it
# builds and tests. Two rules enforce that:
#
# 1. The only trigger type is `labeled`: applying the label approves
# exactly the head commit present at that moment. Later pushes do NOT
# re-fire the workflow; a maintainer must re-apply the label to measure
# a new head, re-establishing trust each time.
# 2. The checkout uses `persist-credentials: false` and no build/test step
# references `secrets`/`github.token`, so the untrusted code under test
# finds no credentials in its environment or in `.git/config`.
#
# One-time setup: create the label and point Settings → Pages at the
# `gh-pages` branch after the first successful run.

name: Code coverage

on:
pull_request_target:
branches: [main]
types: [labeled]

concurrency:
group: code-coverage-${{ github.event.pull_request.number }}
cancel-in-progress: true

permissions:
contents: write # push to gh-pages
pull-requests: write # comment on PR

env:
CARGO_TERM_COLOR: always

jobs:
coverage:
name: Measure and publish coverage
if: github.event.label.name == 'measure code coverage'
runs-on: ubuntu-24.04
timeout-minutes: 90
steps:
- name: Checkout PR head
uses: actions/checkout@v6
with:
ref: ${{ github.event.pull_request.head.sha }}
submodules: recursive
persist-credentials: false

- name: Set up Rust toolchain
uses: actions-rust-lang/setup-rust-toolchain@v1
with:
rustflags: ""
components: llvm-tools-preview

- name: Install solc
uses: ./.github/actions/get-solc

- name: Install Geth
run: |
sudo add-apt-repository -y ppa:ethereum/ethereum
sudo apt update
sudo apt install -y ethereum

- name: Download LLVM
uses: ./.github/actions/get-llvm
with:
target: x86_64-unknown-linux-gnu

- name: Set LLVM environment variables
run: |
echo "LLVM_SYS_221_PREFIX=$(pwd)/llvm-x86_64-unknown-linux-gnu" >> $GITHUB_ENV

- name: Install cargo-llvm-cov
uses: taiki-e/install-action@v2
with:
tool: cargo-llvm-cov

- name: Generate HTML coverage report
# Mirrors `make coverage`. `--ignore-run-fail` keeps partial
# coverage on test-binary non-zero exit. Tests spawn `resolc` as a
# subprocess; resolving it from the instrumented target directory
# makes those invocations contribute coverage.
run: |
cargo llvm-cov clean --workspace
rm -rf target/llvm-cov
export PATH="$PWD/target/llvm-cov-target/debug:$PATH"
cargo llvm-cov --workspace \
--exclude revive-llvm-builder \
--all-targets \
--locked \
--ignore-run-fail \
--html \
--output-dir target/llvm-cov

- name: Generate coverage summary
run: |
cargo llvm-cov report --summary-only \
| tee target/llvm-cov/summary.txt

- name: Upload coverage HTML as workflow artifact
if: success()
uses: actions/upload-artifact@v4
with:
name: coverage-html-pr-${{ github.event.pull_request.number }}
path: target/llvm-cov/html
retention-days: 30

- name: Publish coverage to gh-pages
if: success()
uses: peaceiris/actions-gh-pages@v4
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: target/llvm-cov/html
destination_dir: pr-${{ github.event.pull_request.number }}
# Preserve reports from other PRs already on the branch.
keep_files: true
commit_message: "coverage: PR #${{ github.event.pull_request.number }} @ ${{ github.event.pull_request.head.sha }}"
user_name: github-actions[bot]
user_email: 41898282+github-actions[bot]@users.noreply.github.com

- name: Comment coverage report URL on PR
if: success()
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
const summary = fs.readFileSync('target/llvm-cov/summary.txt', 'utf8').trim();
const owner = context.repo.owner;
const repo = context.repo.repo;
const prNumber = context.issue.number;
const sha = context.payload.pull_request.head.sha.substring(0, 8);
const url = `https://${owner}.github.io/${repo}/pr-${prNumber}/`;
const marker = '<!-- code-coverage-bot -->';
const body = [
marker,
`### Code coverage`,
``,
`Report for \`${sha}\` is available at: ${url}`,
``,
`<details><summary>Summary</summary>`,
``,
`\`\`\``,
summary,
`\`\`\``,
``,
`</details>`,
``,
`_Workflow run: [#${context.runId}](${context.serverUrl}/${owner}/${repo}/actions/runs/${context.runId})_`,
].join('\n');

// Update prior bot comment in place rather than spamming.
const { data: comments } = await github.rest.issues.listComments({
owner, repo, issue_number: prNumber, per_page: 100,
});
const existing = comments.find(c => c.body && c.body.includes(marker));
if (existing) {
await github.rest.issues.updateComment({
owner, repo, comment_id: existing.id, body,
});
} else {
await github.rest.issues.createComment({
owner, repo, issue_number: prNumber, body,
});
}
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,7 @@ playwright-report
.cache
emsdk
docs-tmp
# Coverage HTML staged by `make coverage` and its rendered copy under docs/.
/book/src/coverage/
/docs/coverage/
workdir
59 changes: 59 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
install-wasm \
install-llvm-builder \
install-llvm \
install-llvm-coverage \
install-cargo-llvm-cov \
install-revive-runner \
format \
clippy \
Expand All @@ -19,6 +21,8 @@
test-wasm \
test-llvm-builder \
test-book \
coverage \
coverage-reset \
bench \
bench-pvm \
bench-evm \
Expand All @@ -45,6 +49,14 @@ install-llvm: install-llvm-builder
git submodule update --init --recursive --depth 1
revive-llvm build --llvm-projects lld --llvm-projects clang

# Build LLVM with `LLVM_BUILD_INSTRUMENTED_COVERAGE=On` so `llvm-cov`
# reports cover C++ as well as Rust. Shares the `target-llvm/<env>/`
# tree with `install-llvm`; run `revive-llvm clean` between variants.
# `JOBS=N` caps thread count via CMAKE_BUILD_PARALLEL_LEVEL.
install-llvm-coverage: install-llvm-builder
git submodule update --init --recursive --depth 1
CMAKE_BUILD_PARALLEL_LEVEL=$(JOBS) revive-llvm build --llvm-projects lld --llvm-projects clang --enable-coverage

install-revive-runner:
cargo install --locked --force --path crates/runner --no-default-features

Expand Down Expand Up @@ -87,10 +99,57 @@ test-llvm-builder:
@echo "warning: the llvm-builder tests will take many hours"
cargo test --package revive-llvm-builder -- --test-threads=1

install-cargo-llvm-cov:
@command -v cargo-llvm-cov >/dev/null 2>&1 || cargo install cargo-llvm-cov --locked
@rustup component add llvm-tools-preview >/dev/null 2>&1 || true

test-book:
cargo install mdbook --version 0.5.1 --locked
mdbook test book

# Coverage over `test-workspace` (excludes revive-llvm-builder).
# Stages HTML under book/src/coverage/ and stamps the chapter in place;
# revert with `make coverage-reset`.
coverage: install install-cargo-llvm-cov
cargo install mdbook --version 0.5.1 --locked
cargo llvm-cov clean --workspace
rm -rf target/coverage
PATH="$(CURDIR)/target/llvm-cov-target/debug:$$PATH" \
cargo llvm-cov --workspace \
--exclude revive-llvm-builder \
--all-targets \
--locked \
--ignore-run-fail \
--html \
--output-dir target/coverage
cargo llvm-cov report --summary-only > target/coverage/summary.txt
rm -rf book/src/coverage
mkdir -p book/src/coverage
mv target/coverage/html book/src/coverage/html
mv target/coverage/summary.txt book/src/coverage/summary.txt
@COMMIT=$$(git rev-parse --short HEAD 2>/dev/null || echo unknown) ; \
TIMESTAMP=$$(date -u +"%Y-%m-%dT%H:%M:%SZ") ; \
COVERED=$$(awk '/^TOTAL/ { print $$10; exit }' book/src/coverage/summary.txt) ; \
[ -n "$$COVERED" ] || COVERED=N/A ; \
awk -v commit="$$COMMIT" -v ts="$$TIMESTAMP" -v covered="$$COVERED" \
-v link="../coverage/html/index.html" \
-f .github/scripts/stamp-coverage-chapter.awk \
book/src/developer_guide/coverage.md \
> book/src/developer_guide/coverage.md.new
mv book/src/developer_guide/coverage.md.new book/src/developer_guide/coverage.md
mdbook build book
@echo
@echo "Coverage collected. Run 'make book' to view."
@echo "Run 'make coverage-reset' before committing."

# Restore the committed chapter and drop the staged HTML.
coverage-reset:
cargo install mdbook --version 0.5.1 --locked
git checkout -- book/src/developer_guide/coverage.md
rm -rf book/src/coverage
mdbook build book
@echo "Coverage chapter restored."

bench: install-bin
cargo criterion --all --all-features --message-format=json \
| criterion-table > crates/benchmarks/BENCHMARKS.md
Expand Down
1 change: 1 addition & 0 deletions book/src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
- [IR reference](./developer_guide/newyork_ir.md)
- [PVM and the pallet-revive runtime target](./developer_guide/target.md)
- [Testing strategy](./developer_guide/testing.md)
- [Code coverage](./developer_guide/coverage.md)
- [Cross compilation](./developer_guide/cross_compilation.md)
- [FAQ](./faq.md)
- [Roadmap and Vision](./roadmap.md)
Expand Down
58 changes: 58 additions & 0 deletions book/src/developer_guide/coverage.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# Code coverage

Revive measures Rust line and branch coverage with
[`cargo-llvm-cov`](https://github.com/taiki-e/cargo-llvm-cov) over the same
crate set as `make test-workspace` (the workspace minus
`revive-llvm-builder`).

Two ways to obtain a report:

1. **Locally:** `make coverage` (described below).
2. **Per PR:** apply the `measure code coverage` label. The workflow at
`.github/workflows/code-coverage.yml` publishes the report to
`https://paritytech.github.io/revive/pr-<N>/` and comments the link on
the PR. Label requires maintainer privileges.

## Running locally

```bash
make coverage
```

The target:

1. Builds and tests the workspace under cargo-llvm-cov with
`--ignore-run-fail` so partial coverage lands even when test binaries
exit non-zero.
2. Writes HTML to `target/coverage/html/` and a summary to
`target/coverage/summary.txt`.
3. Stages the HTML under `book/src/coverage/`; mdbook copies it to
`docs/coverage/` during build.
4. Rewrites the **Status** block below in place with the short commit hash,
ISO-8601 UTC timestamp, and total line coverage, then runs
`mdbook build`.

Prerequisites:

* `LLVM_SYS_221_PREFIX` exported and pointing at a compatible LLVM build
(same as `make test-workspace`).
* `llvm-tools-preview` rustup component, `cargo-llvm-cov`, and the pinned
`mdbook` version — all installed on demand by the target.

### Reverting local changes

`make coverage` modifies `book/src/developer_guide/coverage.md` in place and
stages files under `book/src/coverage/`. To restore the committed state
before pushing:

```bash
make coverage-reset
```

## Status

<!-- COVERAGE_STATUS_BEGIN -->
**Last collected:** 2026-07-06T18:46:06Z for commit `0db01dcb` — **62.79% line coverage**.

[Open the report](../coverage/html/index.html)
<!-- COVERAGE_STATUS_END -->
Loading
Loading