Summary#
Runs your app's tests against a Platform running on your own machine — no account, no network, no cloud. It is the local counterpart of Running tests: the same tests, the same throwaway-copy isolation, the same streamed report. The only difference is where it runs.
Signature#
osy test [path] [--filter <text>] [--test <id>] [--pixels] [--headed] [--json]Description#
Start a local platform, then test against it#
A local platform is a full Platform that runs on your machine, with a database it manages itself. Start one from your project directory:
osyrin devThe first start downloads a small database bundle once; after that it is up in seconds. It binds to your machine only — nothing outside can reach it — and it needs no account and no cloud project. Leave it running in a terminal.
In another terminal, run your tests against it:
osy testThat is the whole loop: edit your source, osy test, watch it turn green. There is nothing to log into and nothing to
deploy — osy test finds the running local platform for your project on its own, ensures your app exists there, and
runs your tests. Without one running it does not need you to start one either: it uses the shared test platform
(see Which platform your tests run on, below).
osy test always compiles CURRENT source — never run osy compile first#
osy test (and osy check, whose test leg is the same command) reads your .osy files from disk on every run
and compiles them into their own throwaway branch — never into the app you might have open in a browser. So:
- Editing a test or your model and re-running
osy testneeds nothing in between. There is no build-then-test cycle and no way to be fooled by a stale compile: the branch is built from what is on disk right now, every time. osy compileis a different thing, for a different purpose: it mints a new version of the app you can open in a browser (osy launchdoes the same, plus opening it). It updates what you SEE; it has no effect on whatosy test/osy checksee, because they never look at that app at all.- So running
osy compilebeforeosy test"to make sure the changes are picked up" does nothing except cost the time the compile takes — the test run was always going to see today's source either way.
osy test # compiles current source into a throwaway branch and runs it — every time, unconditionally
osy compile # updates the BROWSABLE dev app — unrelated to what osy test/osy check see--pixels — the same tests, in a real browser#
osy test renders your pages in a headless DOM with no font engine and no compositor. That is what makes it fast
enough to run on every change, and it means the run is blind to layout: a control is "visible" whether it is on
its card, off it, or underneath something else.
osy test --pixels runs the same tests in a real browser instead. Nothing about your test file changes — the
same locators, the same refusals — but the geometric claims in Layout assertions — is it actually usable on screen? are actually judged, and
Ui.Shot("label") writes a PNG you can open.
osy test # behaviour, in seconds
osy test --pixels # the same tests, with layout checked and screenshots written
osy test --pixels --headed # …and show the browser, slowed down, so you can watch it driveIt is opt-in because it costs a browser: the tier needs Playwright and a Chromium, or the Chrome already on your machine, which it prefers. The check runs before anything is compiled or booted, so a machine that cannot run it is told in a second — with the command that fixes it — rather than a minute into a run.
And it REFUSES rather than falling back. On a machine with no browser, osy test --pixels runs nothing and exits
non-zero, saying in its first clause that no test was executed and handing back your own command with the flag
removed. This is what makes the flag safe to put in a gate: a green from osy test --pixels means a browser really
rendered the pages. A run that started the tier and then selected no test that drives the screen is non-zero too, for
the same reason. Plain osy test is unaffected either way — the refusal belongs to the flag. See
Layout assertions — is it actually usable on screen?.
The tests run exactly as they would anywhere#
Local is not a weaker mode. Your app's own security is enforced just as it is in production: a [Test] runs as an
anonymous, secured caller, so a test that creates or reads data needs your model to grant it — a plain new app is
secure by default. This is the same behavior described in Running tests; the point of running locally is
speed and privacy, never a relaxed rulebook. The starter model a new project ships with grants exactly what its first
test needs and nothing more.
Everything else is identical to a remote run: each [TestFixture] seeds its own private branch, each [Test] forks
its own throwaway clone, results stream back one at a time, and nothing a test writes survives it. See
Running tests for the full model.
A test never imports your app's data files — its rows are what its fixture seeds. But the files those data files
store (product photos under public/…) do travel with the run, at the paths osy import would give them, so a
fixture row pointing at one finds it: see [[local-importing-data#in-tests]].
Choosing what to run#
The same two ways as a remote run:
--filter <text>runs the tests whose names containtext. For several, repeat the flag or separate them with,or|(--filter totals|refund): a test runs when its name contains any of them. A pattern that matches no test fails the run, naming the pattern, even when another pattern matched something, so a mistyped name can't drop tests from a run that still reports green.--test <id>runs exactly one test, named by its id (file::fixture::name). Repeat the flag for several.
A fixture is never filtered away, and a [Skip("reason")] test is always reported and never runs.
Scripting a run#
--json writes one JSON object per line, in the order events happen, so a script can react to each test as it lands.
The command exits non-zero when any test fails or errors, and diagnostics go to standard error — standard output stays
a clean stream of events. The event shape is the same as a remote run.
Which platform your tests run on#
You do not have to start one. When no platform is serving your project, osy test runs on the shared test
platform of the build you are using — one local platform for every project you test with that build, each project an
app of its own on it, every run in throwaway copies of its own. Several projects can be tested at once, from several
terminals or checkouts, and they share one database server instead of starting one each. The first run starts it and
says so; the rest find it already up:
Shared test platform ready at http://127.0.0.1:8132 · instance test-platform-3107f8dc — every project tested with this build runs on it; it stops after an idle hour, or `osy stop --test-platform`In order, osy test uses:
- the platform you named with
--devname(orOSY_DEVNAME), exactly as named; - your project's own platform, when it is already running on this build — the one
osyrin devorosy launchstarted — so you test on the platform you are looking at, and nothing else is started; - otherwise the shared test platform.
A different build of the toolchain gets a shared test platform of its own, so a checkout testing its own change to
the platform never runs on another build's server. Nothing about a run changes with where it runs: the same source, the
same isolation, the same report. osy debug-test, osy trace and osy inspect look on the same platform, so the
osy inspect <trace> a failing test prints finds its fault.
OSY_TEST_PLATFORM changes the choice: project always tests on your project's own platform, as a project-per-platform
setup did before; any other value names a shared test platform of its own, which a script can stop and delete by that
name when it is done (OSY_TEST_PLATFORM=nightly osy stop --test-platform --delete).
The shared test platform belongs to no project: stopping your project's platform with osy stop leaves it running for
the others (and says so), another project starting its own platform never stops it, and it stops by itself after an
idle hour. osy stop --test-platform stops it at once — see Stopping the local platform.
Examples#
The two-terminal loop — one platform, many test runs:
# terminal 1
osyrin dev
# terminal 2
osy test
osy test --filter Totals
osy test --filter 'Totals|Refund'
osy test --test "tests/orders.test.osy::Seeded::Totals_Add_Up"A first test in a freshly scaffolded project, which passes because the starter model grants it:
// model/note.osy
entity Note {
[Required] string Title;
security { allow read, create when IsAuthenticated || IsAnonymous; }
}// tests/note.test.osy
[Test]
void a_new_note_keeps_its_title() {
var note = new Note { Title = "First note" };
Assert.Equal("First note", note.Title);
}See also#
Running tests — the same run against a remote platform, and the full description of fixtures, isolation, and the streamed report.
Debugging tests locally — debug a single test locally with breakpoints and stepping in your editor.
Holding a test's world open — --hold: run one test and keep the world it built serving, opened in your browser as the
test's principal, until you stop it.
Running a local platform — the local platform these tests run against, and how to start it.