Examples are realistic demonstrations of the package for the people who will use it. They are where most new visitors land, and for many they are the first Roc code written against the package that they read. This chapter covers what an example is for, and how the same example is kept correct in two places: the repository, and each release.
Risks and controls for example identity§
Examples that pass in the working tree can fail after packaging because files are omitted, dependency URLs are rewritten incorrectly, or a warm cache hides which bundle was fetched. Test the consumer-facing archive as an artifact. Stage the declared files, verify that intended dependency substitutions are the only changes, and run the archive against candidate bundles with an empty package cache. Inspect and reject unsafe archive members before extraction. These checks establish the tested archive’s behavior for its recorded cases; they do not make the cases representative of every product feature.
What an example is for§
The audience of an example is a user of the package who is probably not familiar with it. It is not a contributor.
An example is not a test, a benchmark or a fixture. Those have their own
directories and their own standards. Code that exists to cover an edge case, to
measure something, or to feed another tool does not belong under examples/,
however convenient it is to put it there.
Manage examples with the care their position deserves:
Realistic. Each one solves a problem a user would recognise, with input and output worth looking at.
Focused. Each one demonstrates one thing. Several short examples serve a reader better than one that shows everything.
Documented. Each one carries ample comments that walk the reader through the code: what it is about to do, why it is done that way, and what to change to adapt it. Write them for someone reading top to bottom with no other context.
High quality. An example is copied into real projects. It should be code the maintainers would be content to see copied.
Few. Every example is a promise to keep it working. Add one when it teaches something the others do not.
Why examples live in two places§
The repository and its releases are rarely in step. Pull requests land continuously; releases happen now and then. At most commits the package in the repository is ahead of the last release.
An example in the repository must work with the package at that same commit, so it may use an API that no release has yet. A user reading it beside the latest release could copy code that does not compile for them. An example published with a release has the opposite property: it works with that release and will never change.
One set of files cannot be both. The repository therefore keeps examples that follow the working tree, and each release publishes a frozen copy of them that names that release.
Repository examples test the working tree§
Every example names the package by relative path, as described in
Roc applications in the repository.
A platform’s examples name it the same way, pf: platform "../../platform/main.roc",
and run against the host outputs the repository built under platform/.
A pull request that changes the package and breaks an example fails on that pull
request.
Committed examples are never rewritten after a release. No release follow-up pull request exists, and the default branch never names its own release URL.
Record expected output§
An example that only compiles proves little. Record what each example prints, and compare it on every run. Examples are run by the same driver as the integration tests; see Integration tests.
Keep expected output in
tests/spec.json, in the same suite format the examples archive and the nightly runner use, with the releaseworking-treeand no dependencies: the arguments each example runs with, its standard input, and the exact standard output, standard error and exit code expected.release-package test-examplesruns every example and fails on a difference; the shared release workflow, or a platform’s own release workflow, runs it on every pull request. It needs only the compiler, so it runs on Windows too.release-package test-examples --updaterewrites the expected output, so that an intended change is a reviewed diff.An example that cannot run in CI, such as one needing a display or a console runtime, is still checked and built, and the specification says so explicitly. It is never silently skipped.
The examples archive§
Each release attaches an examples archive named
<name>-examples-<version>.zip, where <name> is the repository, or the
package when the repository publishes several. It contains:
complete copies of the examples, including sibling modules and data files, in which the package dependency is that release’s bundle URL;
the expected-output specification those examples were tested with; and
a short README naming the release, the bundle URL and how to run an example.
An example is a file examples/<name>.roc, which depends on the package as
../package/main.roc, or a directory examples/<name>/ whose main.roc
depends on it as ../../package/main.roc and whose other files travel with it.
A .roc file under examples/ without an app header is a module the
examples share. An example names at most one platform; one that names none runs
on the compiler’s default platform. Each case runs in its example’s own
directory, as a reader would run it, so an example reads a sibling data file by
relative path; the working tree’s examples run in a scratch copy, so an example
that writes files beside itself leaves the tree as it was. Every file Git
tracks under examples/ is archived, so a build output that is not committed
is never published.
Publish the versioned spec.json and ZIP layout in
Publish the suite contract
so that the same immutable archive is executable by the central nightly runner.
Nothing else in an example changes. The archived examples differ from the repository’s at the tested commit only in the package dependency, and the check script verifies that.
app [main!] {
cli: platform "https://github.com/roc-lang/basic-cli/releases/download/<version>/<hash>.tar.zst",
- ansi: "../package/main.roc",
+ ansi: "https://github.com/<owner>/roc-ansi/releases/download/<tag>/<hash>.tar.zst",
}
A user who wants a starter downloads the archive for the release they are installing. The archive is also the fixed input to nightly compatibility, which is why it carries its own expected output: the default branch’s specification describes the default branch, not the release.
Examples of expensive work, such as fuzzing or benchmarking, expose a small, bounded mode suitable for routine checks. Archive cases use that mode to verify the published API and executable behavior; they do not launch sustained searches or produce performance comparisons. Keep campaigns in their own scheduled workflow, outside both PR dry runs and the shared published-release compatibility verdict.
Build and test the exact archive§
The archive that is tested is the archive that is published. Nothing is rebuilt between the test and the upload.
Build the bundle from the tested commit with
roc bundle. Its filename is its content hash and the release tag is the version being released, so the final URL,.../releases/download/<tag>/<hash>.tar.zst, is known before anything is published. A release with several bundles puts a package name before the hash; see Several bundles in one release.Build the archive with that real release URL in every example.
Serve the bundle from a loopback address.
Run the archived examples with
--replace-dep <release URL> <loopback URL>, whichroc check,roc testandroc buildaccept. The files are unchanged; only the download location differs for the duration of the test.Compare the output with the archived specification.
Publish the same bundle and the same archive.
Run the test with an empty Roc package cache, so that the bundle is fetched and verified and not found from an earlier run.
Dry runs§
A pull request runs the release steps with a placeholder version and publishes
nothing. Roc rejects 0.0.0 in a package URL, so the placeholder is
0.0.1-dryrun.
An archive is never reformatted§
An archive is immutable, so it is checked, tested, built and run, and never
passed to roc fmt. A later formatter change would fail the nightly check for a
reason that no patch release can fix.