Build each crate's integration tests as one binary (#4087)

* Build each crate's integration tests as one binary

Every tests/*.rs file is its own executable that links the whole crate
graph. Move each crate's test files into tests/it/ as modules of one
tests/it/main.rs, for apps/pdfcraft, apps/pdfcraft-cli, automation, cos,
crypt, engine, render, sign and ui-egui. ui-egui's ui_group binary and
the support modules move into tests/it/ too. Crates with a single test
file keep it. The test executables go from 150 to 52, and every crate
runs the same number of tests as before.

ui-egui perf keeps its own binary (own process): its frame-time budgets
would charge each measurement for the other tests running beside it.

What the move needs:
- include_str!/include_bytes! and #[path] paths gain one "../".
- ui-egui: one GPU lock in tests/it/main.rs replaces the five per-file
  ones (content, document_links, editing, fill_sign, view), so one
  process creates one wgpu device at a time. The drop-line test in ui.rs
  and the screenshot blocks behind PDFCRAFT_*_SHOT(S) and
  CRAFT_UI_DOCKING_DIR, which render without holding it, take it too.
- pdfcraft winit_wayland_scroll: the vendored winit source it compiles
  names crate::event, so that shim lives in main.rs.
- parity evidence names the new paths.

* Name the one-binary test paths in docs and comments

- READMEs, docs, ATTRIBUTION and source comments name tests/it/<file>.rs.
- The oracle README's example filters with --test it encryption::qpdf_agrees.
- pdfcraft's GPU lock comment points at the UI tests' shared lock in
  crates/ui-egui/tests/it/main.rs.
This commit is contained in:
aiongg authored and GitHub committed 2026-10-22 11:28:58 -04:00
1 parent 80b7d21525
commit 9526363152
146 files changed
+716 -593

No files matched your search

+3 -3
View File
@@ -275,15 +275,15 @@ Every asset PdfCraft includes, bundles or uses to build its published material,
| `assets/icons/thumbs-down.svg` | Lucide icon "thumbs-down" | Lucide Contributors (portions Cole Bemis, Feather) | ISC | https://github.com/lucide-icons/lucide (npm lucide-static 1.49.0, icons/thumbs-down.svg) | UI icon, embedded via crates/ui-egui/src/icon_data.rs |
| `assets/icons/message-square-plus.svg` | Lucide icon "message-square-plus" | Lucide Contributors (portions Cole Bemis, Feather) | ISC | https://github.com/lucide-icons/lucide (npm lucide-static 1.49.0, icons/message-square-plus.svg) | UI icon, embedded via crates/ui-egui/src/icon_data.rs |
| `assets/icons/circle-x.svg` | Lucide icon "circle-x" | Lucide Contributors (portions Cole Bemis, Feather) | ISC | https://github.com/lucide-icons/lucide (npm lucide-static 1.49.0, icons/circle-x.svg) | UI icon, embedded via crates/ui-egui/src/icon_data.rs |
| `crates/ui-egui/tests/data/form.pdf` | One-page AcroForm test fixture (text, check box, radio group, combo box) | PdfCraft contributors | MIT OR Apache-2.0 | Contributor-original: hand-written PDF objects, no third-party content | Form-filling UI tests (crates/ui-egui/tests/forms.rs, commands.rs) |
| `crates/ui-egui/tests/data/form.pdf` | One-page AcroForm test fixture (text, check box, radio group, combo box) | PdfCraft contributors | MIT OR Apache-2.0 | Contributor-original: hand-written PDF objects, no third-party content | Form-filling UI tests (crates/ui-egui/tests/it/forms.rs, commands.rs) |
| `assets/icons/pentagon.svg` | Lucide icon "pentagon" | Lucide Contributors (portions Cole Bemis, Feather) | ISC | https://github.com/lucide-icons/lucide (npm lucide-static 1.49.0, icons/pentagon.svg) | UI icon, embedded via crates/ui-egui/src/icon_data.rs |
| `assets/icons/message-square-quote.svg` | Lucide icon "message-square-quote" | Lucide Contributors (portions Cole Bemis, Feather) | ISC | https://github.com/lucide-icons/lucide (npm lucide-static 1.49.0, icons/message-square-quote.svg) | UI icon, embedded via crates/ui-egui/src/icon_data.rs |
| `crates/sign/tests/data/openssl-signed.pdf` | One-page PDF signed with OpenSSL CMS (adbe.pkcs7.detached) by the RSA test key | PdfCraft contributors | MIT OR Apache-2.0 | Contributor-original: hand-written PDF objects; signature made with `openssl cms -sign` over its byte ranges (see crates/sign/tests/data/README.md) | Signature validation tests (crates/sign/tests/pdf.rs): a signature PdfCraft did not make |
| `crates/sign/tests/data/openssl-signed.pdf` | One-page PDF signed with OpenSSL CMS (adbe.pkcs7.detached) by the RSA test key | PdfCraft contributors | MIT OR Apache-2.0 | Contributor-original: hand-written PDF objects; signature made with `openssl cms -sign` over its byte ranges (see crates/sign/tests/data/README.md) | Signature validation tests (crates/sign/tests/it/pdf.rs): a signature PdfCraft did not make |
| `assets/icons/clipboard-paste.svg` | Lucide icon "clipboard-paste" | Lucide Contributors (portions Cole Bemis, Feather) | ISC | https://github.com/lucide-icons/lucide (npm lucide-static 1.49.0, icons/clipboard-paste.svg) | UI icon, embedded via crates/ui-egui/src/icon_data.rs |
| `assets/fonts/DancingScript.ttf` | Dancing Script (variable) | The Dancing Script Project Authors (Pablo Impallari) | OFL-1.1 | https://github.com/google/fonts/blob/9710da1eacb3be272583c3224dcb70f9da6eadbb/ofl/dancingscript/DancingScript%5Bwght%5D.ttf | Fill & Sign typed signatures and initials: the typed name's glyph outlines, drawn as filled paths (the font file itself is not embedded in PDFs) |
| `packaging/macos/dmg/background.svg` | PdfCraft macOS DMG window background | @XusBadia | MIT OR Apache-2.0 | Original work for this repository, derived from the PdfCraft app icon (assets/app-icon/pdfcraft.svg, referenced via an SVG image element, not copied) | Finder window of the macOS DMG; rendered to background.tiff by packaging/macos/dmg/generate.py |
| `packaging/macos/dmg/background.tiff` | PdfCraft macOS DMG window background (rendered) | @XusBadia | MIT OR Apache-2.0 | Rendered from packaging/macos/dmg/background.svg by packaging/macos/dmg/generate.py | 1x + 2x HiDPI 16-colour palette TIFF copied into the macOS DMG by packaging/macos/package.sh; includes the app icon and the free LittleCMS built-in sRGB profile |
| `crates/sign/tests/data/x509-rsa-sha1.pdf` | One-page PDF with a legacy adbe.x509.rsa_sha1 signature by the RSA test key | PdfCraft contributors | MIT OR Apache-2.0 | Contributor-original: hand-written PDF objects; signature made with `openssl dgst -sha1 -sign` over its byte ranges (see crates/sign/tests/data/README.md) | Signature validation tests (crates/sign/tests/pdf.rs): the legacy adbe.x509.rsa_sha1 format |
| `crates/sign/tests/data/x509-rsa-sha1.pdf` | One-page PDF with a legacy adbe.x509.rsa_sha1 signature by the RSA test key | PdfCraft contributors | MIT OR Apache-2.0 | Contributor-original: hand-written PDF objects; signature made with `openssl dgst -sha1 -sign` over its byte ranges (see crates/sign/tests/data/README.md) | Signature validation tests (crates/sign/tests/it/pdf.rs): the legacy adbe.x509.rsa_sha1 format |
| `assets/icons/monitor.svg` | Lucide icon "monitor" | Lucide Contributors (portions Cole Bemis, Feather) | ISC | https://github.com/lucide-icons/lucide (npm lucide-static 1.49.0, icons/monitor.svg) | UI icon, embedded via crates/ui-egui/src/icon_data.rs |
| `crates/ui-egui/src/i18n/vi.tsv` | Vietnamese interface translation catalog | PdfCraft contributors | MIT OR Apache-2.0 | Contributor-original: clean-room translations of PdfCraft's English UI labels; PhotoCraft and VectorCraft Vietnamese catalogs used only as translation memory when English source and semantic context matched; original catalog reviewed by a native Vietnamese-speaking contributor; 2026-10-15 coverage additions are AI-assisted and not separately native-reviewed; 2026-10-18 page-gap labels are contributor-original AI-assisted translations from English, not separately native-reviewed | Vietnamese UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs |
| `crates/ui-egui/src/i18n/sl.tsv` | Slovene interface translation catalog | PdfCraft contributors | MIT OR Apache-2.0 | Contributor-original: clean-room translations of PdfCraft's English UI labels, no proprietary localisation resources; 2026-10-18 page-gap labels are contributor-original AI-assisted translations from English, not separately native-reviewed | Slovene UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs |
+3 -3
View File
@@ -3277,7 +3277,7 @@ source = "Contributor-original: hand-written PDF objects, no third-party content
licence = "MIT OR Apache-2.0"
licence_file = "LICENSE-MIT"
kind = "document"
usage = "Form-filling UI tests (crates/ui-egui/tests/forms.rs, commands.rs)"
usage = "Form-filling UI tests (crates/ui-egui/tests/it/forms.rs, commands.rs)"
sha256 = "c6f8b656e225101129aa36bd5467adc8dba16730915f19caca17f6a546d0b040"
[[asset]]
@@ -3310,7 +3310,7 @@ source = "Contributor-original: hand-written PDF objects; signature made with `o
licence = "MIT OR Apache-2.0"
licence_file = "LICENSE-MIT"
kind = "document"
usage = "Signature validation tests (crates/sign/tests/pdf.rs): a signature PdfCraft did not make"
usage = "Signature validation tests (crates/sign/tests/it/pdf.rs): a signature PdfCraft did not make"
sha256 = "8b91c3b4e39c4a31717a0dd0685bd7d0468671df32aee5b9818d44dc72c2bc3c"
[[asset]]
@@ -3403,7 +3403,7 @@ source = "Contributor-original: hand-written PDF objects; signature made with `o
licence = "MIT OR Apache-2.0"
licence_file = "LICENSE-MIT"
kind = "document"
usage = "Signature validation tests (crates/sign/tests/pdf.rs): the legacy adbe.x509.rsa_sha1 format"
usage = "Signature validation tests (crates/sign/tests/it/pdf.rs): the legacy adbe.x509.rsa_sha1 format"
sha256 = "b77bfb21166f4bd043597e45a1ea28b9d9f4ba6df0142b3a0fc8b7f8e0fc111b"
[[asset]]
+1 -1
View File
@@ -24,7 +24,7 @@ pdfcraft-platform = { workspace = true }
base64 = "0.22"
[dev-dependencies]
# Decodes `render` output in tests/owner_password.rs; already a dependency of the engine.
# Decodes `render` output in tests/it/owner_password.rs; already a dependency of the engine.
png = "0.18"
[target.'cfg(unix)'.dependencies]
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
+24
View File
@@ -0,0 +1,24 @@
//! The crate's integration tests, linked into one test binary.
mod audit;
mod automation_roots;
mod batch;
mod check;
mod edit;
mod extract;
mod help;
mod info_standards;
mod mcp_compact;
mod options;
mod owner_password;
mod render;
mod root_arguments;
mod run_report;
mod run_root;
mod split;
mod stderr;
mod stdout;
mod text;
mod ui_control;
mod version_resource;
mod xfa;
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
+1 -1
View File
@@ -780,7 +780,7 @@ mod tests {
/// graphics libraries at a time. The test threads otherwise run them together, and on
/// FreeBSD's CI machine the test process then died with SIGSEGV, in about half the runs:
/// the first device was still being set up when the second test began its own. The UI
/// tests take turns the same way (`crates/ui-egui/tests/view.rs`). Hold it for the whole
/// tests take turns the same way (`crates/ui-egui/tests/it/main.rs`). Hold it for the whole
/// test (declared before the instance, so it's released after the device is dropped).
static GPU: std::sync::Mutex<()> = std::sync::Mutex::new(());
+21
View File
@@ -0,0 +1,21 @@
//! The crate's integration tests, linked into one test binary.
// Compile winit's scroll policy against its real event type, without a compositor. The vendored
// source (`winit_wayland_scroll`) names it as `crate::event`.
#[cfg(target_os = "linux")]
mod event {
pub use winit::event::TouchPhase;
}
mod winit_macos_emoji_picker;
mod winit_macos_ime_commit;
mod winit_macos_ime_discard;
mod winit_macos_korean_first_key;
mod winit_macos_korean_space;
mod winit_macos_title_bar_drag;
mod winit_wayland_scroll;
mod winit_wayland_text_input;
mod winit_windows_hidden_maximized;
mod winit_windows_ime_cursor;
mod winit_x11_dpi;
mod winit_x11_ime_focus;
@@ -3,11 +3,8 @@
use egui::{Event, Modifiers, MouseWheelUnit, RawInput, TouchPhase, vec2};
use wayland_client::protocol::wl_pointer::AxisSource;
// Compile the production policy against winit's real event type, without a compositor.
mod event {
pub use winit::event::TouchPhase;
}
#[path = "../../../vendor/winit/src/platform_impl/linux/wayland/seat/pointer/scroll.rs"]
use crate::event;
#[path = "../../../../vendor/winit/src/platform_impl/linux/wayland/seat/pointer/scroll.rs"]
mod scroll;
fn egui_phase(phase: event::TouchPhase) -> TouchPhase {
File renamed without changes.
+1 -1
View File
@@ -128,7 +128,7 @@ claude mcp add pdfcraft -- pdfcraft-cli mcp --root <dir>
1. Add the engine capability first, with its tests (the tool is a thin adapter).
2. Add a `ToolDef` in `src/tools.rs`: a precise description, the schema, `ro()`/`destructive()`, and `cmd()` if a registry command exists.
3. Handle it in `Automation::call`, validating pages and positions with the existing helpers.
4. Add an end-to-end test in `tests/automation.rs`. `tool_table_is_well_formed` checks names, schemas and command links.
4. Add an end-to-end test in `tests/it/automation.rs`. `tool_table_is_well_formed` checks names, schemas and command links.
## Moving several page objects
@@ -539,7 +539,7 @@ fn tool_table_is_well_formed() {
#[test]
fn explicit_font_and_disjoint_ranges_save_reopen_through_tools() {
let dir = workdir("font-ranges");
std::fs::write(dir.join("inter.ttf"), include_bytes!("../../../assets/fonts/Inter-Regular.ttf")).unwrap();
std::fs::write(dir.join("inter.ttf"), include_bytes!("../../../../assets/fonts/Inter-Regular.ttf")).unwrap();
let mut a = auto(&dir);
std::fs::write(dir.join("broken.otf"), b"not a font").unwrap();
let found = ok(&mut a, "text_font_files", json!({}));
File renamed without changes.
File renamed without changes.
+9
View File
@@ -0,0 +1,9 @@
//! The crate's integration tests, linked into one test binary.
mod automation;
mod automation_roots;
mod batch;
mod button_caption;
mod form_bulk;
mod hostile_args;
mod optimize_content_boundaries;
File renamed without changes.
File renamed without changes.
File renamed without changes.
+8
View File
@@ -0,0 +1,8 @@
//! The crate's integration tests, linked into one test binary.
mod bounded_writer;
mod corpus;
mod encryption;
mod proptests;
mod shared_streams;
mod writer;
File renamed without changes.
File renamed without changes.
File renamed without changes.
+4
View File
@@ -0,0 +1,4 @@
//! The crate's integration tests, linked into one test binary.
mod proptests;
mod qpdf_literals;
File renamed without changes.
@@ -278,7 +278,7 @@ fn metadata_flag_from_outside_changes_the_file_key_it_certifies() {
// `/EncryptMetadata` false folds four set bytes into a revision 4 key (Algorithm 2, step f) and
// changes what revision 6 stores in `/U`. qpdf wrote both variants of each, so an implementation
// that ignored the flag would give identical keys. Internally this is no longer untested -
// `tests/proptests.rs` from #2358 varies the flag and asserts the keys differ - but internal tests
// `tests/it/proptests.rs` from #2358 varies the flag and asserts the keys differ - but internal tests
// still share our derivation, so what these rows add is a second implementation's own numbers.
for (enc_label, plain_label) in [("r4-aes128-meta-enc", "r4-aes128-meta-plain"), ("r6-aes256-meta-enc", "r6-aes256-meta-plain")] {
let a = CASES.iter().find(|c| c.label == enc_label).expect("encrypted-metadata fixture present");
@@ -1,7 +1,5 @@
//! Document font selection and contextual glyph save/reopen acceptance on synthetic PDFs.
mod support {
pub mod font_fixture;
}
use crate::support;
use pdfcraft_cos::{Dict, Document, Object, SaveOptions, Stream};
use pdfcraft_engine::{BlockStyle, Edit, Session};
use pdfcraft_render::{PageRenderer, RenderRequest};
File renamed without changes.
+12
View File
@@ -0,0 +1,12 @@
//! The crate's integration tests, linked into one test binary.
mod support {
pub mod font_fixture;
}
mod document_fonts;
mod form_rotation;
mod marked_content_isolation;
mod ocr_edit;
mod private_image_resize;
mod private_text_glyphs;
File renamed without changes.
+4 -4
View File
@@ -15,7 +15,7 @@ Every test that wanted an oracle used to probe for it by hand and decide on its
it was missing. The common choice — print a line and return — reports as a **pass**, so a suite
with no oracles installed looked exactly like one in which every oracle agreed.
That was not hypothetical. No CI runner installed `qpdf`, so `cos/tests/encryption.rs::qpdf_agrees`
That was not hypothetical. No CI runner installed `qpdf`, so `cos/tests/it/encryption.rs::qpdf_agrees`
had never executed on a runner, while `README.md` said saved output was "independently checked with
qpdf". For R2–R4 that test is the *only* check of owner-password authentication (Algorithm 7) that
does not go through our own code.
@@ -50,10 +50,10 @@ Each returns `Result<Oracle, Absent>`. `Oracle::command()` hands back a fresh `C
install the tools, so a skip notice there becomes a red build.
```
$ cargo test -p pdfcraft-cos --test encryption qpdf_agrees
$ cargo test -p pdfcraft-cos --test it encryption::qpdf_agrees
test result: ok. 1 passed # qpdf absent: skipped, with a notice on stderr
$ PDFCRAFT_ORACLES_REQUIRED=1 cargo test -p pdfcraft-cos --test encryption qpdf_agrees
$ PDFCRAFT_ORACLES_REQUIRED=1 cargo test -p pdfcraft-cos --test it encryption::qpdf_agrees
oracle `qpdf` is required but is not on PATH (program not found): PDFCRAFT_ORACLES_REQUIRED is
set, so this is a failure rather than a skip (apt install qpdf / brew install qpdf)
test result: FAILED. 1 failed
@@ -90,7 +90,7 @@ appearance we do draw.
**Poppler is not a usable oracle for JBIG2.** The 96 `bitmap-*.pdf` corpus fixtures encode one
picture through every corner of the format, so a correct decoder renders them all identically. We
render 1 distinct image across all 96; Poppler renders 8, drawing a blank page for seven of them.
`crates/render/tests/jbig2_invariants.rs` pins that invariant, and needs no oracle to do it.
`crates/render/tests/it/jbig2_invariants.rs` pins that invariant, and needs no oracle to do it.
## Comparing text
+6
View File
@@ -0,0 +1,6 @@
//! The crate's integration tests, linked into one test binary.
mod inline_image_ei;
mod jbig2_invariants;
mod named_form_properties;
mod search;
File renamed without changes.
+1 -1
View File
@@ -2,7 +2,7 @@
//!
//! Verification only, on public data, so plain affine arithmetic over `crypto-bigint` is enough:
//! no secret is involved, and a verification takes a few milliseconds. Checked against
//! signatures made by OpenSSL (`tests/crypto.rs`).
//! signatures made by OpenSSL (`tests/it/crypto.rs`).
use rsa::BoxedUint;
+2 -2
View File
@@ -45,14 +45,14 @@ A synthetic one-page PDF signed the legacy way (`/SubFilter /adbe.x509.rsa_sha1`
the key of `rsa-aes.p12`. Built by writing the objects with a fixed-width `/ByteRange` and zero-filled
`/Contents`, patching the byte range, then `openssl dgst -sha1 -sign rsa.key` over the two ranges.
### RSA signature variants (hex constants in `tests/crypto.rs`)
### RSA signature variants (hex constants in `tests/it/crypto.rs`)
Over SHA-256("hello") with the key of `rsa-aes.p12`:
`openssl dgst -sha256 -sign rsa.key` (standard DigestInfo); `openssl pkeyutl -sign -pkeyopt rsa_padding_mode:pkcs1`
over a hand-built DigestInfo without the NULL parameter, and over the bare digest;
`openssl dgst -sha256 -sigopt rsa_padding_mode:pss -sigopt rsa_pss_saltlen:0 -sign rsa.key`.
### Other algorithms (hex constants in `tests/algorithms.rs`)
### Other algorithms (hex constants in `tests/it/algorithms.rs`)
All sign the message `hello` with throw-away keys, OpenSSL 3.6. RSA ones use the key of `rsa-aes.p12`.
File renamed without changes.
File renamed without changes.
@@ -192,7 +192,7 @@ fn verdict(leaf: Party, embedded: Vec<Certificate>, anchor: &Certificate) -> (St
let trust = TrustStore { certs: vec![anchor.clone()] };
let s = signatures(&Document::open(Arc::new(signed.clone())).unwrap(), &signed, &trust).into_iter().rfind(|s| s.signed).unwrap();
// The details as they read in English; what an interface says in another language is checked in
// `tests/pdf.rs`.
// `tests/it/pdf.rs`.
(s.status, s.details.iter().map(|d| d.message.clone()).collect())
}
@@ -105,7 +105,7 @@ fn the_signed_content_type_is_the_type_of_the_encapsulated_content() {
assert!(why.contains("not the encapsulated content type"), "{why}");
// Without a content-type attribute there is nothing to compare here. The type is kept, and a
// PDF signature's validation refuses one that is not data (tests/pdf.rs).
// PDF signature's validation refuses one that is not data (tests/it/pdf.rs).
let sd = SignedData::parse(&signed_data(&id, SIGNED_DATA, std::slice::from_ref(&message_digest))).unwrap();
assert_eq!(sd.econtent_type, SIGNED_DATA);
}
File renamed without changes.
+9
View File
@@ -0,0 +1,9 @@
//! The crate's integration tests, linked into one test binary.
mod algorithms;
mod ber;
mod chain;
mod cms;
mod crypto;
mod pdf;
mod store_keys;
@@ -2182,11 +2182,15 @@ fn appearance_label_outside_helvetica_is_refused_beside_an_embedded_font_name()
/// the files. An interface then has these to translate and only these.
#[test]
fn wordings_are_complete() {
let source = include_str!("../src/pdf.rs");
let source = include_str!("../../src/pdf.rs");
// The modules below `pdf` word whole sentences too: how a signature value had to be read, and
// what a certificate does not allow.
let elsewhere =
[include_str!("../src/x509.rs"), include_str!("../src/cms.rs"), include_str!("../src/keys.rs"), include_str!("../src/rsa_pad.rs")];
let elsewhere = [
include_str!("../../src/x509.rs"),
include_str!("../../src/cms.rs"),
include_str!("../../src/keys.rs"),
include_str!("../../src/rsa_pad.rs"),
];
/// A sentence said to the user, as opposed to an OID, a name or a tag: it reads as English.
fn is_sentence(s: &str) -> bool {
s.len() > 24 && s.ends_with('.') && s.contains(' ') && s.starts_with(|c: char| c.is_ascii_uppercase())
File renamed without changes.
+2 -2
View File
@@ -499,7 +499,7 @@ mod tests {
/// `script_fallback` honours the documented installed-font switch, under which it returns
/// nothing before order is ever consulted — so both loader tests and the macOS oracle skip when
/// it is off, the way `tests/fonts.rs` skips for the same reason. Without this the suite goes red in exactly the
/// it is off, the way `tests/it/fonts.rs` skips for the same reason. Without this the suite goes red in exactly the
/// mode `cargo xtask screenshots` uses.
fn installed_faces_switched_off() -> bool {
std::env::var_os("PDFCRAFT_SYSTEM_FONTS").is_some_and(|v| v == "0")
@@ -527,7 +527,7 @@ mod tests {
#[test]
fn a_script_fallback_is_the_first_candidate_with_the_probe() {
// This one calls `script_fallback` too, so it fails under the documented switch that
// published screenshots use — skip with a reason, as tests/fonts.rs does.
// published screenshots use — skip with a reason, as tests/it/fonts.rs does.
if installed_faces_switched_off() {
eprintln!("skipping: PDFCRAFT_SYSTEM_FONTS=0 turns the loader off");
return;
File renamed without changes.
File renamed without changes.
@@ -181,6 +181,7 @@ fn search_finds_collapsed_bookmarks_and_keeps_original_paths() {
assert!(h.query_by_label("Other chapter").is_none());
assert_eq!(h.state().session.save_full_bytes(id).unwrap(), before, "search doesn't edit the PDF");
if let Ok(dir) = std::env::var("PDFCRAFT_BOOKMARK_SHOTS") {
let _gpu = crate::gpu();
h.render().unwrap().save(std::path::Path::new(&dir).join("filtered-bookmarks.png")).unwrap();
}
menu(&mut h, "Résumé results", "Delete");
@@ -243,6 +244,7 @@ trailer << /Root 1 0 R >>
h.get_by_label("Method");
assert_eq!(h.state().session.get(id).unwrap().can_undo(), Some("New bookmarks from structure"));
if let Ok(dir) = std::env::var("PDFCRAFT_BOOKMARK_SHOTS") {
let _gpu = crate::gpu();
h.render().unwrap().save(std::path::Path::new(&dir).join("bookmarks-from-structure.png")).unwrap();
}
}
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -1,7 +1,7 @@
//! The command registry drives menus, shortcuts and the palette: every registered command must
//! be implemented, disabled commands must say why, and every surface must reach the same action.
mod support;
use crate::support;
use egui::os::OperatingSystem;
use egui::{Key, Modifiers};
@@ -71,14 +71,14 @@ const PICKERS: &[&str] = &[
#[test]
fn every_registered_command_is_implemented() {
for spec in COMMANDS {
// Page-text Copy needs a loaded view and egui context, covered by ui_group::text_select.
// Page-text Copy needs a loaded view and egui context, covered by text_select.
if PICKERS.contains(&spec.id) || spec.id == "edit.copy" {
continue;
}
let mut app = PdfCraftApp::new();
app.set_option("language", "en").unwrap();
if spec.id.starts_with("form.") || spec.id == "comment.flatten" {
app.open_bytes("form.pdf", None, include_bytes!("data/form.pdf").to_vec()).unwrap();
app.open_bytes("form.pdf", None, include_bytes!("../data/form.pdf").to_vec()).unwrap();
} else if spec.id == "protect.unlock" {
// Only a permissions password: opens restricted, and can be unlocked.
let mut doc = pdfcraft_cos::Document::open(std::sync::Arc::new(fixture(3))).unwrap();
@@ -7,7 +7,7 @@ use egui_kittest::kittest::Queryable;
use pdfcraft_render::Annotation;
use pdfcraft_ui_egui::{PdfCraftApp, QuickTool};
mod support;
use crate::support;
/// Two 300×200 pages of Helvetica text.
const TEXT_FIXTURE: &[u8] = b"%PDF-1.7
@@ -145,6 +145,7 @@ fn sidebar_comment_centres_the_annotation_at_high_zoom() {
&& rotation == 0
&& let Ok(dir) = std::env::var("PDFCRAFT_SHOTS")
{
let _gpu = crate::gpu();
settle(&mut h);
h.render().unwrap().save(format!("{dir}/comment-centred.png")).unwrap();
}
@@ -164,6 +164,7 @@ fn compare_filters_hide_text_or_visual_changes() {
#[test]
fn compare_panel_screenshot() {
let Ok(out) = std::env::var("PDFCRAFT_SHOT") else { return };
let _gpu = crate::gpu();
let s = Session::new();
let v1 = s.create_from_text("t", "Delivery within five days. Returns accepted.").unwrap().to_vec();
let v2 = s.create_from_text("t", "Delivery within three days. Returns accepted for a week.").unwrap().to_vec();
@@ -6,23 +6,14 @@ use egui_kittest::Harness;
use egui_kittest::kittest::Queryable;
use pdfcraft_ui_egui::{PdfCraftApp, QuickTool};
/// Tests that render pixels (`h.render()`) take this first, so only one wgpu device exists at a
/// time. Lavapipe crashes when another test creates its Vulkan instance meanwhile (segfault in
/// `vkCreateSampler` during a concurrent `vkCreateInstance`). Declared before the harness, so it
/// is released after the device is dropped.
static GPU: std::sync::Mutex<()> = std::sync::Mutex::new(());
fn gpu() -> std::sync::MutexGuard<'static, ()> {
// A test that panicked while holding it leaves nothing to clean up.
GPU.lock().unwrap_or_else(std::sync::PoisonError::into_inner)
}
use crate::gpu;
fn harness() -> Harness<'static, PdfCraftApp> {
harness_with_step_dt(0.25)
}
fn harness_with_step_dt(step_dt: f32) -> Harness<'static, PdfCraftApp> {
harness_bytes_with_step_dt(include_bytes!("data/form.pdf").to_vec(), step_dt)
harness_bytes_with_step_dt(include_bytes!("../data/form.pdf").to_vec(), step_dt)
}
fn harness_bytes(bytes: Vec<u8>) -> Harness<'static, PdfCraftApp> {
@@ -282,12 +282,14 @@ fn bookmark_titles_can_be_searched_over_control() {
ok(&mut h, &c, "ui.click", json!({ "label": "Target chapter" }));
assert_eq!(h.state().views[0].current, 3);
if let Ok(dir) = std::env::var("PDFCRAFT_BOOKMARK_SHOTS") {
let _gpu = crate::gpu();
h.render().unwrap().save(format!("{dir}/control-filtered-bookmarks.png")).unwrap();
}
ok(&mut h, &c, "ui.click", json!({ "label": "Clear" }));
h.get_by_label("Background");
h.get_by_label("Target chapter");
if let Ok(dir) = std::env::var("PDFCRAFT_BOOKMARK_SHOTS") {
let _gpu = crate::gpu();
h.render().unwrap().save(format!("{dir}/control-cleared-bookmarks.png")).unwrap();
}
}
@@ -1453,6 +1455,7 @@ fn docking_renders_real_pdf_default_floating_and_redocked() {
h.run_steps(5);
h.state().docking.validate().unwrap();
if let Ok(directory) = std::env::var("CRAFT_UI_DOCKING_DIR") {
let _gpu = crate::gpu();
std::fs::create_dir_all(&directory).unwrap();
h.render().unwrap().save(format!("{directory}/pdf-{width}-{scale}-{state}.png")).unwrap();
}
@@ -1499,6 +1502,7 @@ fn home_and_combine_suppress_inspector_without_forgetting_layout_or_choice() {
h.run_steps(4);
assert!(h.query_by_role_and_label(egui::accesskit::Role::Tab, "Inspector").is_none());
if let Ok(directory) = std::env::var("CRAFT_UI_DOCKING_DIR") {
let _gpu = crate::gpu();
std::fs::create_dir_all(&directory).unwrap();
let state = if combine { "combine" } else { "home" };
h.render().unwrap().save(format!("{directory}/availability-{state}.png")).unwrap();
@@ -1511,6 +1515,7 @@ fn home_and_combine_suppress_inspector_without_forgetting_layout_or_choice() {
h.run_steps(4);
h.get_by_label("This document has no bookmarks.");
if let Ok(directory) = std::env::var("CRAFT_UI_DOCKING_DIR") {
let _gpu = crate::gpu();
h.render().unwrap().save(format!("{directory}/availability-return.png")).unwrap();
}
assert_eq!(serde_json::to_value(&h.state().docking).unwrap(), before);
@@ -1810,6 +1815,7 @@ fn page_size_shows_while_the_pointer_is_in_the_bottom_left_quadrant() {
h.run_steps(3);
assert!(shown(&h), "bottom-left: shown");
if let Ok(dir) = std::env::var("PDFCRAFT_SHOT_DIR") {
let _gpu = crate::gpu();
h.render().unwrap().save(format!("{dir}/page-size-hint.png")).unwrap();
}
h.event(egui::Event::PointerMoved(away));
@@ -25,6 +25,7 @@ fn image_import_dialog_chooses_dpi_and_cancels() {
});
h.run_steps(3);
if let Ok(path) = std::env::var("PDFCRAFT_IMAGE_IMPORT_SHOT") {
let _gpu = crate::gpu();
h.render().unwrap().save(path).unwrap();
}
h.get_by_label("Use 72 DPI (one point per pixel)").click();
@@ -322,6 +323,7 @@ fn multiple_files_open_as_one_document_in_the_page_grid() {
h.get_by_label_contains("left out: report.docx");
h.get_by_label("Insert a file before page 2");
if let Ok(path) = std::env::var("PDFCRAFT_CREATE_MULTIPLE_SHOT") {
let _gpu = crate::gpu();
h.run_steps(20);
h.render().unwrap().save(path).unwrap();
}
Loaded 100 of 146 files, more files were not shown because too many files have changed in this diff. Show more