Test a viewer
A viewer is a function from an *Ast to a string, so most of its testing is
plain Rust unit tests that run natively, without wasm and without the host. This
page lists the checks, from fastest to slowest.
1. Unit tests for render
Keep rendering in a function that takes the decoded *Ast and returns the HTML,
and put tests beside it in main.rs:
#[cfg(test)]
mod tests {
use super::*;
fn ast(content: &str) -> TextAst {
TextAst { filename: "notes.txt".into(), content: content.into(), ..Default::default() }
}
#[test]
fn empty_content_says_so() {
let html = render(&ast(""));
assert!(html.contains("(empty)") && !html.contains("<table"), "{html}");
}
}
Run one viewer's tests:
cargo test -p poka-text-viewer
Build the *Ast with struct update syntax (..Default::default()) so a field
added to the proto later does not break the test. Put the HTML in the assertion
message, as above, so a failure shows what was rendered.
Cases worth a test in every viewer:
- The empty input. An
*Astwith no rows, no nodes or no content renders a clear "empty" message, not a broken table. - The truncated input. When the proto says the content was capped, the page says how much is shown and how much exists.
- A hostile string in every field (below).
- One card.
test_support::assert_one_card(&html, filename, file_size)checks that the preview has exactly one shared card with each host placeholder once, and that the viewer did not print the file's name or size itself. - Large input. The caps are
memory_mbandmax_time_msinplugin.json. If the viewer builds output proportional to the input, test that a large*Aststays inside them.
2. The hostile string test
poka_viewer_common::test_support exports HOSTILE, a string that breaks out
of a double-quoted attribute, opens a tag and a script, and closes a comment,
and assert_inert, which checks the string appears in the HTML only as escaped
text.
use poka_viewer_common::test_support::{assert_inert, HOSTILE};
#[test]
fn every_string_from_the_file_is_escaped() {
let a = TextAst {
filename: HOSTILE.into(),
source_format: HOSTILE.into(),
content: format!("{HOSTILE}\nsecond"),
..Default::default()
};
assert_inert(&render(&a));
}
Fill every string field the viewer shows. assert_inert fails when the string is
missing from the output, so a field the test forgot or the viewer does not render
shows up as a failure rather than a silent pass.
3. Garbage on stdin
A viewer must not panic on bytes that are not its *Ast. The decode step
returns an error and the viewer prints error_html; check that path by calling
the decode on a few byte strings (empty, one byte, random) and confirming that
you get the error HTML back.
4. The whole workspace
cargo test --workspace
cargo build --release --workspace --target wasm32-wasip1
CI runs both. The first runs every viewer's tests natively, the second builds
every viewer to wasm. A viewer that compiles natively can still fail to build for
wasm32-wasip1 (a dependency that uses threads or sockets, for example), so run
the wasm build before you push.
One thing to know when adding a viewer: viewers/poka-viewer-common/src/theme.rs
has a test, every_viewer_uses_the_shared_theme, that counts the *-viewer
directories with assert_eq!(seen, ...) and checks that each uses THEME_CSS.
A new viewer changes that count, so update it in the same PR.
5. The manifest and the repo checks
python3 -m pip install -r build/scripts/requirements.txt # pinned jsonschema, once
python3 build/scripts/validate-viewer-plugin.py # schema and source policy
python3 build/scripts/validate-viewer-ids.py --parsers ../poka-parsers/parsers
python3 -m unittest discover -s build/scripts/tests # the validators' own tests
python3 build/scripts/viewer-inventory.py --check # the coverage table is current
python3 build/scripts/test-manifests.py # cross-checks the manifests
python3 build/scripts/check-docs.py --check # after touching docs/
validate-viewer-ids.py --parsers needs the poka-parsers sibling. It confirms
that every viewer id a parser names exists here. CI cannot run that part,
because poka-parsers is private, so it only runs locally.
These are the commands in README.md, "Testing", which also lists the licence
and third-party notices checks. Say in your PR what you ran and what you did not.
6. See it in the app
A viewer is only reached through a parser, so the end-to-end check is a real file
through a parser that names your viewer. Build and install both
(python3 build/scripts/install-extensions.py here, and the parsers' installer
in poka-parsers) and open a file in the host. The workspace-level steps are in
the poka-parsers tutorial "Write a parser".