Padding counted as pixel data: a file padded with junk could still claim a canvas up to
max_pixels, which the decoder allocates before it finds the data missing.
- Uncompressed BMP: the rows the header describes (bits per pixel, 4-byte row padding) must
all be present after the pixel-data offset; the decoder reads every row in full.
- RLE BMP: the commands are walked as the decoder paints them (runs clipped at the row's end,
end-of-row, delta, end-of-bitmap); bytes after the end draw nothing.
- GIF: the first frame's LZW codes are walked up to the end code, keeping only the length of
each table string; data sub-blocks after the end code draw nothing.
A canvas the data leaves mostly blank stays limited to 4 MP. New tests: one padded file per
case (all four were accepted before), and the LZW walk against frames the encoder wrote.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016TuX1qw9ieyuJ1ZFrrUxzc
From echelon's review of #2607:
- A GIF's LZW data now bounds its first frame's own width x height, not the logical screen's,
so a valid GIF with a small first frame on a larger screen imports.
- A canvas the file's data leaves mostly blank (a GIF screen larger than its first frame, or an
RLE BMP larger than its runs could cover at 255 pixels per 2 bytes) still costs its whole size
in from_decoder, so it is limited to 4 MP (SPARSE_MAX_PIXELS) and refused before the decoder
allocates it. Densely coded canvases keep the max_pixels limit.
Tests: a 64-byte RLE BMP declaring 2001 x 2000 or 32000 x 32000 is refused ("mostly blank");
the RLE BMP that ends early still opens at 2000 x 2000; a GIF with a 1 x 1 first frame opens on
a 2000 x 2000 screen and is refused on a 32767 x 32767 one. The first three fail without this
change.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PtjHGt5gnCHXgLt4NbGorf
* CI: dev profile keeps line tables for workspace crates and no debug info for dependencies
* Control server tests: persistent_requests_and_slow_replies_outlive_the_idle_read_timeout no longer races the idle timeout
Each request (and the closing half-shutdown) reaches the socket before the
worker reads, so on a slow machine only the reply waits outlast the 100 ms
idle timeout, as the test intends. The idle test sends its input the same
way, and the no-frames test allows 5 s instead of 2 s to give up.
* CI: one integration-test binary per crate
Each crate's tests/*.rs become modules of tests/it/main.rs (51 test files
into 13 binaries, plus 6 that stay their own). Inner #![cfg] attributes
move to the mod lines. Files that change state for the whole process keep
a binary of their own: codecs jxl_tail_heap and raw sr2_heap (dhat global
allocator), gpu shutdown, shutdown_first and memory, ui-egui shutdown.
Docs, comments and the GPU probe name the new paths and targets
(--test it <module>::); the CI report names an it binary's skips after
the crate.
* CI: tests under nextest, doctests apart, one wasm check
xtask ci runs the tests with cargo nextest run --lib --bins --tests when
cargo-nextest is installed, else cargo test with the same targets and a
one-line notice; doctests run after them with cargo test --workspace --doc.
The heif and hardware GPU steps use the same runner. Examples stay out of
the test build (clippy --all-targets still checks them). Concurrent tests
under nextest follow the RUST_TEST_THREADS cap (NEXTEST_TEST_THREADS).
The wasm check is one cargo check --keep-going over the same crates, and
clippy runs with --keep-going.
* CI: tests run under cargo test by default, nextest with LIGHTCRAFT_NEXTEST=1 (per-process scene cache)