Skip to content

test: load test workspaces into the compiler instead of copying them - #561

Merged
magnus-madsen merged 16 commits into
masterfrom
test/direct-workspace-loading
Jul 28, 2026
Merged

test: load test workspaces into the compiler instead of copying them#561
magnus-madsen merged 16 commits into
masterfrom
test/direct-workspace-loading

Conversation

@magnus-madsen

@magnus-madsen magnus-madsen commented Jul 28, 2026

Copy link
Copy Markdown
Member

Test workspaces are no longer copied into test/activeWorkspace before each suite. Every suite now hands its .flix files to the compiler by URI and content, so they are compiled where they live in test/testWorkspaces, and blanks them again when it finishes. Nothing is written to disk, so no file-system watcher is involved and there is nothing to clean up.

A full run goes from ~2 minutes to ~18 seconds.

Extension changes

  • New test-only command flix.addUri, alongside the existing flix.checkCount and flix.allJobsFinished.
  • handleAddUri forwards an optional src, which the compiler uses instead of reading the file from disk — the same inline-source path updateUri already used for unsaved editor changes. Without src, behaviour is unchanged.

Test changes

  • init(name) / teardown(name) replace the copy-based setup. The old init and its 138 lines of supporting code — clearing the workspace, copying a directory into it, and a 500ms quiescence loop covering debounced reconciliation — are gone.
  • Fixture directories hold plain files: no flix.toml, no src/. test/testWorkspaces/codeActions/ is simply Main.flix and Date.flix. Files in a subdirectory are not loaded, which is how a fixture opts out of being part of the program.
  • files.test.ts is the only suite that still touches the file system, since it exists to test that the extension notices files appearing and disappearing. It owns those helpers itself now, and creates and deletes the two artifacts it mutates.
  • test/activeWorkspace is empty before and after a run, and is cleaned before the extension starts if a previous run was interrupted.
  • test/src/util.ts: 518 → 305 lines.

magnus-madsen and others added 16 commits July 28, 2026 16:58
Add `init2`, which hands every `.flix` file of a test workspace directory to
the compiler by URI and content, instead of copying the directory into the
active workspace. The files are compiled where they live in `testWorkspaces`,
so nothing changes on disk and no file-system watcher is involved — the
delete/copy/settle dance of `init` is skipped entirely.

This is backed by two test-only commands, `flix.addUri` and `flix.remUri`,
alongside the existing `flix.checkCount` and `flix.allJobsFinished`.
`handleAddUri` now forwards an optional `src`, which the compiler uses instead
of reading the file from disk, as `updateUri` already did for unsaved changes.

Since no watcher will ever report these files as gone, a suite has to clean up
after itself: `teardown2` blanks the content the compiler holds for them.

`codeActions` is the first suite converted; the rest still use `init`.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Convert the remaining suites to `init2`/`teardown2`, so that only
`files.test.ts` — which is about file-system manipulation — still copies a
workspace into place. A full run drops from ~2 minutes to ~21 seconds.

Most suites only needed their URIs pointed at `testWorkspaces` and a teardown
added. Two did not:

- `completions` creates its file and types into it, so `src/Temp.flix` stays a
  real file in the active workspace; saving into the fixture directory would
  modify a tracked file. It is deleted again in the teardown.
- `diagnostics` no longer copies `latent/X.flix` into `src/`. It loads each
  latent fixture where it lies with `loadFile`, and blanks it again after the
  test. Clearing content is now done with `blankFile` rather than an editor
  edit, which reaches the compiler as the same `api/addUri` job carrying `src`.

Fix an ordering bug in `init2` found by this: on the first suite the compiler
is not connected yet, so the initial workspace scan is enqueued once the socket
opens — after the removals `init2` had already sent, silently re-adding the
previous suite's files. It now waits for the check that scan triggers, but only
when the extension was not already active and the scan found files, which is
exactly when that check is guaranteed.

Remove `copyFile` and `replaceDocumentContent`, which no test uses anymore.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Now that a test workspace is loaded by URI and content rather than copied into
place, it no longer has to look like a Flix project: `codeActions` is simply
`Main.flix` and `Date.flix`. Drop the `src` directory and the `flix.toml` of
every directory loaded by `init2` — nothing reads the manifest, as the compiler
is handed the files directly and `ensureFlixExists` resolves `flix.jar` without
it.

`files` keeps its layout, since `init` still copies it into the active
workspace, where the extension discovers it by glob.

`init2` correspondingly loads the `.flix` files *directly in* the directory.
Subdirectories are now what marks a fixture as dormant, so `diagnostics/latent`
keeps working as before, and `findFixtureFiles` loses its recursion.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Parked in 094c374 to work around a compiler crash on associated effect types
(flix/flix#8326), for a `test.skip`ped implementation test which 878f74e has
since removed. Nothing references it, and neither the extension's glob nor
`init2` ever loaded it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… ones

Four of the six error fixtures can be part of the same program, so `init2` now
loads them like every other suite and the tests are plain assertions on the
resulting diagnostics.

The other two cannot: a duplicate formal parameter (`WeederError.flix`) or a
cycle among type aliases (`ResolutionError.flix`) stops the compiler from
reporting the errors of the other files. Loading all six together leaves only
the weeder error, and holding back just the weeder error leaves the type,
redundancy and safety errors unreported. Those two therefore stay in `latent`
and are loaded for the duration of their own test.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`ResolutionError.flix` used a cycle among type aliases, which suppresses the
errors of every other file, so it had to be loaded on its own. An undefined
name — the most common resolution error, per TestResolver.scala — does not
suppress anything, so it now joins the files `init2` loads, and `latent` holds
only the weeder fixture.

Weeder errors block regardless of which one is used: a non-linear pattern
(TestWeeder.scala) suppresses the other files exactly as the duplicate formal
parameter does, so that fixture stays as it was.

Rewrite `RedundancyError.flix`, which called the long-gone `Files.readLines`
and so reported an undefined name and two type errors on top of the shadowing
it is meant to test. It now reports exactly the two shadowing errors.

Delete `latent/UnusedFunction.flix`: it no longer parses, and an unused
definition is not reported at all in LSP mode — no fixture can test it — which
is why no test ever referenced it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A weeder error suppresses the errors of every other file, so `WeederError.flix`
could never be part of the program the other tests assert on. Dropping it costs
the coverage of that one error class and buys a uniform suite: every diagnostics
fixture is now loaded by `init2` like every other test workspace, and the tests
are plain assertions with no loading, no blanking and no `latent` directory.

Subdirectories are still skipped by `init2`, so a fixture that must stay out of
the program can be put in one and loaded with `loadFile`.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A file is created before it is deleted, and the codebase names the pair that way
round throughout: `api/addUri` / `api/remUri`, `flix.addUri` / `flix.remUri`,
`addFile` / `deleteFile`. The tests stay grouped by artifact.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`normalizeLocation` and `normalizeUri` lost their last caller when 878f74e
removed the ignored implementation tests. `sleep` and `copyDirContents` are only
used inside util, so they no longer need to be exported.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`files.test.ts` was the only caller of `init`, and carried 138 lines of util
with it: clearing the active workspace, copying a whole directory into it, and
`settleAfterChange`, whose 500ms quiescence loop existed to cover the debounced
reconciliation a folder-level create or delete can trigger.

The suite only ever mutates two artifacts, so it now loads `Main.flix` and
`Assert.flix` in place like every other suite, and owns `src/Area.flix` and
`lib/circleArea.fpkg` itself: they are recreated from the fixture's `workspace`
directory before each test and deleted when the suite ends. The `workspace`
directory mirrors the paths they are copied to, and is skipped by `init2` for
being a subdirectory.

The active workspace is therefore empty before and after the run, and
`test/testWorkspaces` no longer holds a `flix.toml` at all.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…piler

Every suite now cleans up after itself, so the active workspace is only dirty
when a run was interrupted. `init2` therefore no longer makes the compiler
forget the files it finds there — it deletes them, and does so before
`ext.activate()`, while no file-system watcher exists to report them.

That removes the race the removal had to work around: the workspace scan is
enqueued when the compiler connects, which happens after `activate()` returns,
so anything sent before that could be undone by it. The special case for the
first suite goes with it.

`flix.remUri` has no callers left, so drop it from the extension.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`init2`, `teardown2`, `loadFile` and `blankFile` counted `lsp/check` responses
to be sure the compiler had reacted. That is only necessary when the extension
hears about a change through a file-system watcher, which fires at a time of its
own choosing.

These four hand the file to the compiler directly, and the notification that
does so travels the same ordered connection as the one `flix.allJobsFinished`
sends. The server enqueues the job as it handles the first notification, so by
the time it handles the second the queue is not empty, and waiting for idle
covers the file — including the check the compiler runs once the queue drains.

Verified over three consecutive full runs.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The provider commands resolve the document from the URI they are given, and
`init2` already opens every fixture document, so showing an editor bought
nothing in five suites. It did have an effect, though: making a file outside the
workspace the active editor is what triggered the extension's warning that the
file is not part of the project, once per opened fixture.

Only the completion test needs an editor, since typing goes to the active one,
so `open` moves to its single call site.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The suite created `src/Temp.flix` in the active workspace, typed into it and
saved, because saving was the only way to leave a clean editor behind for the
next suite. It does not need a file of its own: the extension sends the compiler
the content of the editor, so the typed text reaches it whether or not the
document is saved.

It now types into an empty fixture loaded by `init2` like any other, and reverts
the buffer when the suite ends — which both leaves the fixture untouched on disk
and closes the editor without a save prompt. `typeText` no longer saves.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`addFile`, `deleteFile`, `tryDeleteFile` and the active-workspace URI helper are
only used by the file manipulation suite, now that the completion test types
into an unsaved fixture buffer instead of creating a file of its own. They live
in that suite, below its tests, ordered from the ones the tests call down to the
primitives those are built on.

`util.ts` exports `awaitCheck` and `getFileUri` in their place: `awaitCheck` is
still shared, since `typeText` needs it for the same reason — an editor reports
a change when it sees fit, so the compiler has to be observed reacting to it.

The active-workspace helper is renamed `getWorkspaceDocUri`, which says what
distinguishes it from `getFixtureDocUri` now that the two sit side by side.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The names were taken by the copy-based setup, which no longer exists.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@magnus-madsen
magnus-madsen merged commit caba7b2 into master Jul 28, 2026
8 checks passed
@magnus-madsen
magnus-madsen deleted the test/direct-workspace-loading branch July 28, 2026 19:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant