test(harness): name the root check for what it asserts, and lint the crate

`open_and_refresh_see_an_empty_origin_root` claimed more than it checked:
since the previous commit an empty *entry* is not a document, so `list`
being empty means "no documents", not "no entries". A check whose name
overstates it is the shape this repo keeps finding — a green tick against a
claim nobody verified. Renamed, and `wipe`'s doc now says out loud that it
cannot clear a zero-byte entry, and why that costs nothing.

`harness/opfs-contract` is not a workspace member, so the `test` job's
workspace-wide `cargo fmt --check` and clippy never reach it. The browser
job now runs both against it — it was, in fact, unformatted.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Edward Langley
2026-09-19 22:46:19 -07:00
parent 7e991360df
commit 29962b113f
3 changed files with 87 additions and 53 deletions
+7
View File
@@ -107,6 +107,13 @@ jobs:
curl -sSfL "$url" | tar -xz --strip-components=1 -C /usr/local/bin \
"wasm-pack-${WASM_PACK_VERSION}-x86_64-unknown-linux-musl/wasm-pack"
wasm-pack --version
# Not a workspace member (it only builds for wasm32), so the `test` job's
# workspace-wide fmt and clippy do not reach it.
- name: Lint the harness crate
working-directory: harness/opfs-contract
run: |
cargo fmt --all -- --check
cargo clippy --target wasm32-unknown-unknown --all-targets -- -D warnings
- name: Install the harness
working-directory: harness
run: npm ci
+79 -52
View File
@@ -62,12 +62,18 @@ impl Report {
}
}
/// Remove every entry in the origin root, so a run does not inherit the last
/// one's leftovers.
/// Remove every *document* in the origin root, so a run does not inherit the
/// last one's leftovers.
///
/// Uses only the backend's own vocabulary — `refresh`, `delete`,
/// `commit_deletes` — rather than reaching for `removeEntry` directly, so a
/// broken `delete` shows up here as well as in the contract.
///
/// It therefore cannot clear a zero-byte entry, because `list` no longer
/// names one. That is deliberate and costs nothing: `attach` recreates and
/// `write` fills whatever a previous run left behind, and a leftover empty
/// entry is invisible to every assertion here for the same reason it is
/// invisible to a document picker.
async fn wipe(fs: &OpfsFs) -> Result<usize> {
fs.refresh().await?;
let names = fs.list(Path::new(""))?;
@@ -99,12 +105,15 @@ pub async fn run_checks() -> Result<String, JsError> {
.map_err(|e| JsError::new(&format!("wipe: {e:#}")))?;
report
.run("open_and_refresh_see_an_empty_origin_root", async || {
fs.refresh().await?;
let listed = fs.list(Path::new(""))?;
ensure!(listed.is_empty(), "a wiped root listed {listed:?}");
Ok("empty".into())
})
.run(
"open_and_refresh_see_an_origin_root_holding_no_documents",
async || {
fs.refresh().await?;
let listed = fs.list(Path::new(""))?;
ensure!(listed.is_empty(), "a wiped root listed {listed:?}");
Ok("empty".into())
},
)
.await;
// `attach` must create the file to obtain a sync access handle for a
@@ -114,28 +123,37 @@ pub async fn run_checks() -> Result<String, JsError> {
// the improvise-9x6 browser failure (the seed was skipped as "already
// there" and `load_from` got an empty buffer).
report
.run("an_attached_but_unwritten_entry_is_not_a_document", async || {
fs.attach(Path::new("attached.improv")).await?;
let listed = fs.list(Path::new(""))?;
ensure!(listed.is_empty(), "attach listed {listed:?} before anything was written");
.run(
"an_attached_but_unwritten_entry_is_not_a_document",
async || {
fs.attach(Path::new("attached.improv")).await?;
let listed = fs.list(Path::new(""))?;
ensure!(
listed.is_empty(),
"attach listed {listed:?} before anything was written"
);
// ...and a second instance, which shares no index, must not see
// it either: the filter has to be in `refresh`, not only in the
// omission from `attach`.
let other = OpfsFs::open().await?;
other.refresh().await?;
let seen = other.list(Path::new(""))?;
ensure!(seen.is_empty(), "refresh() listed the empty entry: {seen:?}");
// ...and a second instance, which shares no index, must not see
// it either: the filter has to be in `refresh`, not only in the
// omission from `attach`.
let other = OpfsFs::open().await?;
other.refresh().await?;
let seen = other.list(Path::new(""))?;
ensure!(
seen.is_empty(),
"refresh() listed the empty entry: {seen:?}"
);
// The handle is real, though: writing through it works, and then
// it IS a document.
fs.write(Path::new("attached.improv"), b"now it is one")?;
ensure!(
fs.list(Path::new(""))? == vec![std::path::PathBuf::from("attached.improv")],
"a written document was not listed"
);
Ok("created, unlisted until written".into())
})
// The handle is real, though: writing through it works, and then
// it IS a document.
fs.write(Path::new("attached.improv"), b"now it is one")?;
ensure!(
fs.list(Path::new(""))? == vec![std::path::PathBuf::from("attached.improv")],
"a written document was not listed"
);
Ok("created, unlisted until written".into())
},
)
.await;
// The hand-rolled `keys()` async-iterator walk in `refresh` is the least
@@ -168,7 +186,10 @@ pub async fn run_checks() -> Result<String, JsError> {
let p = Path::new("offsets.improv");
fs.attach(p).await?;
fs.write(p, b"0123456789ABCDEF")?;
ensure!(fs.read(p)? == b"0123456789ABCDEF", "long write did not round-trip");
ensure!(
fs.read(p)? == b"0123456789ABCDEF",
"long write did not round-trip"
);
fs.write(p, b"xy")?;
let short = fs.read(p)?;
ensure!(
@@ -200,32 +221,38 @@ pub async fn run_checks() -> Result<String, JsError> {
// OPFS at the end of the batch. Both halves, because either one alone
// would pass against a backend that got the timing exactly backwards.
report
.run("a_delete_is_visible_at_once_and_lands_on_commit", async || {
wipe(&fs).await?;
let p = Path::new("doomed.improv");
fs.attach(p).await?;
fs.write(p, b"bytes")?;
.run(
"a_delete_is_visible_at_once_and_lands_on_commit",
async || {
wipe(&fs).await?;
let p = Path::new("doomed.improv");
fs.attach(p).await?;
fs.write(p, b"bytes")?;
fs.delete(p)?;
ensure!(fs.read(p).is_err(), "a deleted document was still readable");
ensure!(fs.list(Path::new(""))?.is_empty(), "a deleted document was still listed");
fs.delete(p)?;
ensure!(fs.read(p).is_err(), "a deleted document was still readable");
ensure!(
fs.list(Path::new(""))?.is_empty(),
"a deleted document was still listed"
);
let before = OpfsFs::open().await?;
before.refresh().await?;
ensure!(
before.list(Path::new(""))?.iter().any(|q| q == p),
"the entry left OPFS before commit_deletes — the queue is not a queue"
);
let before = OpfsFs::open().await?;
before.refresh().await?;
ensure!(
before.list(Path::new(""))?.iter().any(|q| q == p),
"the entry left OPFS before commit_deletes — the queue is not a queue"
);
fs.commit_deletes().await?;
let after = OpfsFs::open().await?;
after.refresh().await?;
ensure!(
!after.list(Path::new(""))?.iter().any(|q| q == p),
"commit_deletes did not remove the entry"
);
Ok("queued then removed".into())
})
fs.commit_deletes().await?;
let after = OpfsFs::open().await?;
after.refresh().await?;
ensure!(
!after.list(Path::new(""))?.iter().any(|q| q == p),
"commit_deletes did not remove the entry"
);
Ok("queued then removed".into())
},
)
.await;
Ok(report.finish())
+1 -1
View File
@@ -21,7 +21,7 @@ async function run(page, cmd) {
* Rust on purpose: a check deleted on one side has to be deleted on both,
* which is what stops the battery quietly shrinking. */
const EXPECTED = [
"open_and_refresh_see_an_empty_origin_root",
"open_and_refresh_see_an_origin_root_holding_no_documents",
"an_attached_but_unwritten_entry_is_not_a_document",
"refresh_walks_every_key_and_sorts_them",
"read_and_write_are_absolute_at_offset_zero",