All checks were successful
joule-moq-remote host agent / joule-moq-remote · does this change touch it (pull_request) Successful in 6s
joule-moq-room room service / joule-moq-room · does this change touch it (pull_request) Successful in 7s
joule-moq-web browser client / joule-moq-web · does this change touch it (pull_request) Successful in 6s
standards workspaces build + test / which workspaces this change touches (pull_request) Successful in 6s
wai-app and Create / wai-app · does this change touch it (pull_request) Successful in 6s
wai-audit collector and player integration / does this change touch it (pull_request) Successful in 6s
WAI breaking changes and the conformance runner / WAI breaking · does this change touch WAI (pull_request) Successful in 6s
WAI operation catalogue / does this change touch it (pull_request) Successful in 6s
WAI conformance / does this change touch it (pull_request) Successful in 6s
WAI energy receipts / does this change touch it (pull_request) Successful in 5s
WAI examples / WAI examples · does this change touch them (pull_request) Successful in 6s
wai-fleet service / does this change touch it (pull_request) Successful in 6s
WAI JPEG-AI whole decode (pinned inputs, required) / does this change touch it (pull_request) Successful in 7s
wai-ledger service / does this change touch it (pull_request) Successful in 6s
joule-moq-remote host agent / joule-moq-remote, every target (macos) (pull_request) Successful in 1m25s
wai-pack / wai-pack · does this change touch it (pull_request) Successful in 6s
wai-quantum for JavaScript / does this change touch it (pull_request) Successful in 6s
wai-quantum studio and desktop / does this change touch it (pull_request) Successful in 6s
wai-sink-android native sink library / does this change touch it (pull_request) Successful in 6s
wai-sink-swift native sink package / does this change touch it (pull_request) Successful in 6s
joule-moq-remote host agent / joule-moq-remote, every target (linux-arm64) (pull_request) Successful in 1m57s
repository checks / lockfiles, advisories, tests in CI, workflow hygiene, spec and section references, public text (pull_request) Successful in 2m7s
WAI Studio / does this change touch it (pull_request) Successful in 6s
wai-web reach builds / wai-web reach · does this change touch it (pull_request) Successful in 6s
wai-web wasm32 / does this change touch it (pull_request) Successful in 6s
wai-app and Create / wai-app · tests, clippy, the line language's verifier (linux) (pull_request) Successful in 29s
wai-audit collector and player integration / collector, player integration and the browser end to end · macos (pull_request) Successful in 2m1s
WAI conformance / fast integer kernels, every x86_64 kernel set (AVX-512 silicon) (pull_request) Successful in 2m59s
wai-audit collector and player integration / collector · linux (pull_request) Successful in 1m1s
WAI conformance / cross-arch byte-equality · macos (pull_request) Successful in 26s
standards workspaces build + test / macOS energy backends (pull_request) Successful in 4m19s
WAI conformance / v2 vocabulary · manifests validate as stated (pull_request) Successful in 16s
WAI conformance / codec wrappers · fuzz smoke, text decode limits, C entry points, provenance, the semantic path (pull_request) Successful in 1m4s
WAI breaking changes and the conformance runner / WAI breaking · wire, record and API against main · linux (pull_request) Successful in 1m15s
wai-app and Create / wai-app · Create, browser end-to-end (macos) (pull_request) Successful in 5m15s
joule-moq-room room service / joule-moq-room, every target (pull_request) Successful in 6m19s
wai-fleet service / service · macos (pull_request) Successful in 1m2s
WAI breaking changes and the conformance runner / WAI breaking and conform · their tests, and the independent verifiers through wai conform · linux (pull_request) Successful in 2m46s
WAI conformance / delivery telemetry · CMCD and CMSD against an independent library (pull_request) Successful in 1m43s
wai-ledger service / service · macos (pull_request) Successful in 1m24s
WAI conformance / cross-arch byte-equality · linux-arm64 (pull_request) Successful in 1m2s
WAI operation catalogue / the catalogue and every door · linux (pull_request) Successful in 4m18s
wai-quantum studio and desktop / desktop app · windows (pull_request) Successful in 9m19s
wai-pack / wai-pack · macos (pull_request) Successful in 3m8s
WAI conformance / cross-arch byte-equality · linux (pull_request) Successful in 30s
WAI energy receipts / energy receipts · macos (pull_request) Successful in 7m49s
WAI conformance / C2PA manifest emission · signed, read back, validated (pull_request) Successful in 3m23s
WAI conformance / faces from keypoints under wasm32-wasip2 (wasmtime) (pull_request) Successful in 1m39s
WAI conformance / quantum core and corpus under wasm32-wasip2 (wasmtime) (pull_request) Successful in 3m9s
wai-fleet service / service · linux (pull_request) Successful in 2m5s
wai-quantum studio and desktop / desktop app · macos (pull_request) Successful in 3m28s
wai-ledger service / service · linux (pull_request) Successful in 1m43s
wai-pack / wai-pack · linux (pull_request) Successful in 1m55s
wai-quantum for JavaScript / javascript · linux (pull_request) Successful in 3m0s
WAI Studio / the catalogue's actions, the unit tests and the core · linux (pull_request) Successful in 1m10s
joule-moq-web browser client / joule-moq-web · browser end-to-end (macos) (pull_request) Successful in 16m12s
WAI conformance / each feature of the independent verifiers' steps compiles alone (pull_request) Successful in 11m32s
wai-sink-android native sink library / every ABI, the AAR, and the instrumented tests in a headless emulator · macos (pull_request) Successful in 4m51s
WAI examples / WAI examples · the archive, the readiness check, the browser and Apple · macos (pull_request) Successful in 13m12s
WAI conformance / cargo-class corpora · macos (pull_request) Successful in 15m2s
wai-quantum studio and desktop / studio · browser (pull_request) Successful in 4m3s
WAI conformance / cargo-class corpora · linux-arm64 (pull_request) Successful in 16m34s
wai-web reach builds / wai-web reach builds · the package of record, every output as recorded (pull_request) Successful in 2m40s
standards workspaces build + test / joule-code-rs (joule-code/joule-code-rs, joule-code-rs, linux-arm64, true) (pull_request) Successful in 17m22s
wai-pack / wai-pack · linux-arm64 (pull_request) Successful in 1m44s
wai-quantum studio and desktop / desktop app · linux (pull_request) Successful in 5m46s
wai-pack / wai-pack · the floor through a media prober (pull_request) Successful in 34s
wai-pack / wai-pack · dependency licences (pull_request) Successful in 17s
WAI examples / WAI examples · portable · linux (pull_request) Successful in 9m28s
WAI examples / WAI examples · android-app · linux-arm64 (pull_request) Successful in 5m7s
WAI conformance / cargo-class corpora · linux (pull_request) Successful in 14m49s
wai-web reach builds / wai-web reach builds · this tree's wai-web, every rung on its engine line (pull_request) Successful in 4m34s
wai-sink-android native sink library / the JNI layer and the platform-free half on the host JVM · linux-arm64 (pull_request) Successful in 2m16s
WAI JPEG-AI whole decode (pinned inputs, required) / fetch pinned JPEG-AI inputs → whole decode + pixel hash, required (pull_request) Successful in 11m4s
WAI energy receipts / energy receipts · linux (pull_request) Successful in 13m6s
wai-web wasm32 / wai-web wasm32 build · corpus, menu and writers (pull_request) Successful in 5m58s
wai-sink-swift native sink package / libwai slices, desktop tests, simulator tests · macos (pull_request) Successful in 10m55s
WAI Studio / Studio in Chromium and Firefox · macos (pull_request) Successful in 9m51s
WAI Studio / Studio in WebKit · linux (pull_request) Successful in 10m15s
wai-pack / wai-pack · independent verifier (Python) (pull_request) Successful in 9m21s
WAI energy receipts / energy receipts · linux-arm64 (pull_request) Successful in 30m14s
WAI conformance / independent verifiers · claims, JWP, key discovery and integer payloads (Python) (pull_request) Successful in 1h4m7s
Every change to WAI added an entry at the head of wai/CHANGELOG.md, and every new module appended to the hand-kept lists of .github/workflows/wai-conformance.yml. So any two branches conflicted on those lines and had to merge one after another. This moves both into files a change adds. - Changelog entries: a change adds wai/changes/<yyyy-mm-dd>-<slug>.md holding the entry text. scripts/changelog-assemble.py renders the entries into CHANGELOG.md's Unreleased section for a release, in a stable order. scripts/check-changelog-fragment.py, run by repo-checks on every pull request, fails a pull request that changes wai/ and adds or edits no entry file directly in wai/changes/ (a release passes, and so does a commit with the trailer line "Changelog: none"). A pull request opened before the check reached main passes as it is; one opened after it that adds its entry to the head of Unreleased fails and is told where the entry goes. wai breaking reads breaking changes from entry files too. - The conformance workflow: each per-module verifier step is a file in .github/wai-conformance/verifiers/, each corpus a file in corpora/, and the integer-kernel tests carry a tag line. scripts/wai-conformance-lists.py derives every list the workflow kept by hand (WAI_FEATURES, the union --bin and --test lists, the features-alone lists, the kernel and corpus steps) from those files when the workflow runs. Its `selftest --exact` holds the derived lists to the hand lists of the commit before this one, and its dry-run prints every command each job runs: the dry-runs before and after this change differ only in the gate's path list, which gains the two paths this change adds. - What stays shared, and how to insert there, is written in wai/CONTRIBUTING.md and wai/changes/README.md: wai-rs Cargo.toml features and [[test]] entries, lib.rs, registry rows, wai/.gitattributes and the live corpus table take an insert beside the related entry, never at the end. Co-Authored-By: Claude Opus 5.5 <[email protected]>
285 lines
12 KiB
Rust
285 lines
12 KiB
Rust
// wai-conformance: int-kernels-release
|
|
//! What reading a manifest allocates (SPEC §2, *Reader limits*; SECURITY.md, *Manifest
|
|
//! parsing*). A counting global allocator records every byte the reader asks for, and
|
|
//! each case requires the peak to stay within the bound the reference states: 128 bytes
|
|
//! for each value the manifest holds, one for each byte of its text, and, for a `WAI2`,
|
|
//! one [`RenditionMeta`] for each entry it lists, with 64 KiB for fixed costs. With the
|
|
//! value limit (2^22) and the length limit (2^27) that is at most 640 MiB for a `WAI1`
|
|
//! manifest, whatever its shape.
|
|
//!
|
|
//! The cases: a manifest at the length limit made of small values, refused for its
|
|
//! values before anything is built from it (the reader that parsed such a manifest into a
|
|
//! JSON tree took some 5 GB); one at the length limit within the value limit, which is
|
|
//! read; manifests of small values in each shape at the value limit, read, and one value
|
|
//! past it, refused; the largest manifest a conforming staged layer has, a `prev` with a
|
|
//! different producer for each of 2^20 units; and a `WAI2` listing as many renditions as
|
|
//! its table can hold, read, and one more, refused.
|
|
//!
|
|
//! The allocator counts what is asked of it, so the figures are the same on every 64-bit
|
|
//! target. A reallocation is counted as the new block allocated before the old one is
|
|
//! freed, which is the most it can hold at once. One test runs at a time (`ONE`), so the
|
|
//! count is this reader's alone.
|
|
|
|
use std::alloc::{GlobalAlloc, Layout, System};
|
|
use std::sync::Mutex;
|
|
use std::sync::atomic::{AtomicUsize, Ordering::Relaxed};
|
|
use std::time::Instant;
|
|
|
|
use wai::container::{
|
|
check_manifest_text, RenditionMeta, Wai, WaiMulti, MANIFEST_MAX_LEN, MANIFEST_MAX_VALUES,
|
|
};
|
|
|
|
struct Counting;
|
|
static NOW: AtomicUsize = AtomicUsize::new(0);
|
|
static PEAK: AtomicUsize = AtomicUsize::new(0);
|
|
|
|
fn grow(n: usize) {
|
|
let now = NOW.fetch_add(n, Relaxed) + n;
|
|
PEAK.fetch_max(now, Relaxed);
|
|
}
|
|
|
|
unsafe impl GlobalAlloc for Counting {
|
|
unsafe fn alloc(&self, l: Layout) -> *mut u8 {
|
|
let p = unsafe { System.alloc(l) };
|
|
if !p.is_null() {
|
|
grow(l.size());
|
|
}
|
|
p
|
|
}
|
|
unsafe fn alloc_zeroed(&self, l: Layout) -> *mut u8 {
|
|
let p = unsafe { System.alloc_zeroed(l) };
|
|
if !p.is_null() {
|
|
grow(l.size());
|
|
}
|
|
p
|
|
}
|
|
unsafe fn dealloc(&self, p: *mut u8, l: Layout) {
|
|
unsafe { System.dealloc(p, l) };
|
|
NOW.fetch_sub(l.size(), Relaxed);
|
|
}
|
|
unsafe fn realloc(&self, p: *mut u8, l: Layout, new: usize) -> *mut u8 {
|
|
let q = unsafe { System.realloc(p, l, new) };
|
|
if !q.is_null() {
|
|
grow(new);
|
|
NOW.fetch_sub(l.size(), Relaxed);
|
|
}
|
|
q
|
|
}
|
|
}
|
|
|
|
#[global_allocator]
|
|
static COUNTING: Counting = Counting;
|
|
|
|
static ONE: Mutex<()> = Mutex::new(());
|
|
|
|
/// The most the reference may allocate reading a manifest of `len` bytes holding
|
|
/// `values` values and listing `renditions` `WAI2` entries (SPEC §2; SECURITY.md).
|
|
fn bound(len: usize, values: usize, renditions: usize) -> usize {
|
|
128 * values + len + renditions * std::mem::size_of::<RenditionMeta>() + (1 << 16)
|
|
}
|
|
|
|
/// What `f` allocates at its peak beyond what was held when it started, with its result
|
|
/// (which the peak includes) and the seconds it took.
|
|
fn peak_of<T>(f: impl FnOnce() -> T) -> (T, usize, f64) {
|
|
let base = NOW.load(Relaxed);
|
|
PEAK.store(base, Relaxed);
|
|
let t = Instant::now();
|
|
let r = f();
|
|
(r, PEAK.load(Relaxed) - base, t.elapsed().as_secs_f64())
|
|
}
|
|
|
|
fn envelope(magic: &[u8; 4], m: &str, rows: usize) -> Vec<u8> {
|
|
let mut b = magic.to_vec();
|
|
b.extend_from_slice(&(m.len() as u32).to_le_bytes());
|
|
b.extend_from_slice(m.as_bytes());
|
|
if magic == b"WAI2" {
|
|
b.extend_from_slice(&(rows as u16).to_le_bytes());
|
|
for _ in 0..rows {
|
|
b.extend_from_slice(&[0; 8]);
|
|
}
|
|
} else {
|
|
b.extend_from_slice(&0u32.to_le_bytes());
|
|
}
|
|
b
|
|
}
|
|
|
|
/// A `WAI1` manifest with a member no revision defines, `pad` (a reader ignores it, SPEC
|
|
/// §8), whose value is `open`, `n` elements written by `elem`, and `close`: with `pad`,
|
|
/// 10 values and the elements'.
|
|
const HEAD: &str = r#"{"wai":"1.0","media":"text","intent":"replicate","model_requirement":{"capability":"wai.text.zstd"},"conditioning":{"kind":"zstd"},"target":null,"pad":"#;
|
|
const FIXED: usize = 10;
|
|
|
|
fn padded(n: usize, open: &str, close: &str, mut elem: impl FnMut(usize, &mut String)) -> String {
|
|
let mut m = String::with_capacity(HEAD.len() + 2 + n * 8);
|
|
m.push_str(HEAD);
|
|
m.push_str(open);
|
|
for i in 0..n {
|
|
if i > 0 {
|
|
m.push(',');
|
|
}
|
|
elem(i, &mut m);
|
|
}
|
|
m.push_str(close);
|
|
m.push('}');
|
|
m
|
|
}
|
|
|
|
/// Read `m` as a `WAI1` envelope; the peak and time, and whether it was read.
|
|
fn read_wai1(m: &str) -> (Result<(), String>, usize, f64) {
|
|
let env = envelope(b"WAI1", m, 0);
|
|
peak_of(|| Wai::from_bytes(&env).map(|_| ()).map_err(|e| e.to_string()))
|
|
}
|
|
|
|
fn report(name: &str, len: usize, values: usize, peak: usize, secs: f64, bound: usize) {
|
|
eprintln!(
|
|
"{name}: {len} bytes, {values} values: peak {peak} bytes ({:.2} per byte, {:.1} per value) in {secs:.2} s; bound {bound}",
|
|
peak as f64 / len as f64,
|
|
peak as f64 / values as f64
|
|
);
|
|
assert!(peak <= bound, "{name}: the read allocated {peak} bytes, past the bound {bound}");
|
|
}
|
|
|
|
/// A manifest at the length limit made of small values is refused from its value count,
|
|
/// having built nothing: the first pass holds no value, and stops at the value past the
|
|
/// limit, some 8 MB into the text.
|
|
#[test]
|
|
fn a_manifest_of_small_values_at_the_length_limit_is_refused_before_it_is_built() {
|
|
let _one = ONE.lock().unwrap_or_else(|e| e.into_inner());
|
|
// Zeros, the last of them a number of ones long enough to bring the text to exactly
|
|
// 2^27 bytes.
|
|
let k = (MANIFEST_MAX_LEN - HEAD.len() - 3) / 2;
|
|
let mut m = padded(k, "[", "]", |_, m| m.push('0'));
|
|
let short = MANIFEST_MAX_LEN - m.len();
|
|
let last = m.len() - 3;
|
|
m.replace_range(last..last + 1, &"1".repeat(short + 1));
|
|
assert_eq!(m.len(), MANIFEST_MAX_LEN);
|
|
let (r, peak, secs) = read_wai1(&m);
|
|
let e = r.expect_err("refused");
|
|
assert!(e.starts_with("manifest_too_many_values:"), "{e}");
|
|
report("2^27 bytes of zeros (refused)", m.len(), MANIFEST_MAX_VALUES + 1, peak, secs, 1 << 16);
|
|
}
|
|
|
|
/// A manifest at the length limit holding exactly the value limit is read within the
|
|
/// bound: 2^22 values, the most a reader must accept, each a string, to 2^27 bytes.
|
|
#[test]
|
|
fn a_manifest_at_both_limits_is_read_within_the_bound() {
|
|
let _one = ONE.lock().unwrap_or_else(|e| e.into_inner());
|
|
let n = MANIFEST_MAX_VALUES - FIXED;
|
|
// Each string `"…"` and its comma: share out what is left of 2^27 bytes.
|
|
let room = MANIFEST_MAX_LEN - HEAD.len() - 3 - (n - 1) - 2 * n;
|
|
let (q, r) = (room / n, room % n);
|
|
let m = padded(n, "[", "]", |i, m| {
|
|
m.push('"');
|
|
m.extend(std::iter::repeat_n('s', q + usize::from(i < r)));
|
|
m.push('"');
|
|
});
|
|
assert_eq!(m.len(), MANIFEST_MAX_LEN);
|
|
assert_eq!(check_manifest_text(m.as_bytes()).unwrap(), MANIFEST_MAX_VALUES);
|
|
let (r, peak, secs) = read_wai1(&m);
|
|
r.expect("read");
|
|
report("2^27 bytes, 2^22 strings", m.len(), MANIFEST_MAX_VALUES, peak, secs, bound(m.len(), MANIFEST_MAX_VALUES, 0));
|
|
}
|
|
|
|
/// Manifests of small values, in every shape that costs a reader the most for each
|
|
/// value, at exactly the value limit, are read within the bound; one value more is
|
|
/// refused (`manifest_too_many_values`).
|
|
#[test]
|
|
fn small_values_at_the_value_limit_are_read_within_the_bound_and_one_more_is_refused() {
|
|
let _one = ONE.lock().unwrap_or_else(|e| e.into_inner());
|
|
let n = MANIFEST_MAX_VALUES - FIXED;
|
|
type Shape = (&'static str, usize, &'static str, &'static str, fn(usize, &mut String));
|
|
let shapes: [Shape; 6] = [
|
|
("an array of zeros", n, "[", "]", |_, m| m.push('0')),
|
|
("an array of empty arrays", n, "[", "]", |_, m| m.push_str("[]")),
|
|
("an array of empty objects", n, "[", "]", |_, m| m.push_str("{}")),
|
|
("an array of one-element arrays", n / 2, "[", "]", |_, m| m.push_str("[0]")),
|
|
("an array of one-member objects", n / 2, "[", "]", |_, m| m.push_str(r#"{"a":0}"#)),
|
|
("one object of many members", n, "{", "}", |i, m| {
|
|
m.push('"');
|
|
m.push_str(&format!("{i:x}"));
|
|
m.push_str("\":0");
|
|
}),
|
|
];
|
|
for (name, k, open, close, elem) in shapes {
|
|
// A shape of two values an element takes one zero more when `n` is odd.
|
|
let mut m = padded(k, open, close, elem);
|
|
if k != n && n % 2 == 1 {
|
|
m.insert_str(m.len() - 2, ",0");
|
|
}
|
|
let values = check_manifest_text(m.as_bytes()).unwrap();
|
|
assert_eq!(values, MANIFEST_MAX_VALUES, "{name}");
|
|
let (r, peak, secs) = read_wai1(&m);
|
|
r.expect(name);
|
|
report(name, m.len(), values, peak, secs, bound(m.len(), values, 0));
|
|
|
|
m.insert_str(m.len() - 2, if open == "{" { r#","x":0"# } else { ",0" });
|
|
let (r, peak, _) = read_wai1(&m);
|
|
let e = r.expect_err("one value past the limit is refused");
|
|
assert!(e.starts_with("manifest_too_many_values:"), "{name}: {e}");
|
|
// The first pass holds the names of an object's members to find a repeat, so the
|
|
// refusal of one object of many members holds them; it stays within the bound.
|
|
assert!(peak <= bound(m.len(), MANIFEST_MAX_VALUES, 0), "{name}: the refusal allocated {peak} bytes");
|
|
}
|
|
}
|
|
|
|
/// The largest manifest a conforming staged layer has (SPEC §2): a `prev` that names a
|
|
/// different producer for each of the 2^20 units a `WIR1` layer may cover, about 94 MB
|
|
/// and 3·2^20 values, is read within the bound, and its `refines` reads well formed.
|
|
#[test]
|
|
fn the_largest_conforming_layer_manifest_is_read_within_the_bound() {
|
|
let _one = ONE.lock().unwrap_or_else(|e| e.into_inner());
|
|
let (a, b) = ("ab".repeat(32), "cd".repeat(32));
|
|
let units = 1usize << 20;
|
|
let mut m = String::with_capacity(95 << 20);
|
|
m.push_str(r#"{"wai":"1.0","media":"image","intent":"replicate","model_requirement":{"capability":"wai.image.int_refine","fallback":null},"conditioning":{"kind":"int_refine"},"refines":{"base":""#);
|
|
m.push_str(&a);
|
|
m.push_str(r#"","prev":["#);
|
|
for i in 0..units {
|
|
if i > 0 {
|
|
m.push(',');
|
|
}
|
|
m.push_str(r#"{"producer":""#);
|
|
m.push_str(if i % 2 == 0 { &a } else { &b });
|
|
m.push_str(r#"","count":1}"#);
|
|
}
|
|
m.push_str(r#"],"layer":2},"target":null}"#);
|
|
let values = check_manifest_text(m.as_bytes()).unwrap();
|
|
assert_eq!(values, 3 * units + 14);
|
|
let env = envelope(b"WAI1", &m, 0);
|
|
let (w, peak, secs) = peak_of(|| Wai::from_bytes(&env).expect("read"));
|
|
report("a layer naming 2^20 producers", m.len(), values, peak, secs, bound(m.len(), values, 0));
|
|
let r = w.refines().expect("refines").expect("well formed");
|
|
assert_eq!(r.units(units as u32), (0, units as u32));
|
|
r.check_units(units as u32).expect("the runs cover the units");
|
|
}
|
|
|
|
/// A `WAI2` manifest listing the 65 535 renditions its table can hold is read within the
|
|
/// bound, which counts the entries it builds; a `renditions` of 65 536 is refused before
|
|
/// its entries are built.
|
|
#[test]
|
|
fn a_wai2_listing_its_tables_rows_is_read_within_the_bound() {
|
|
let _one = ONE.lock().unwrap_or_else(|e| e.into_inner());
|
|
let head = r#"{"wai":"1.1","media":"text","intent":"replicate","renditions":["#;
|
|
let manifest = |rows: usize| {
|
|
let mut m = String::from(head);
|
|
for i in 0..rows {
|
|
if i > 0 {
|
|
m.push(',');
|
|
}
|
|
m.push_str(r#"{"capability":"wai.text.zstd","kind":"zstd"}"#);
|
|
}
|
|
m.push_str(r#"],"target":null}"#);
|
|
m
|
|
};
|
|
let rows = u16::MAX as usize;
|
|
let m = manifest(rows);
|
|
let values = check_manifest_text(m.as_bytes()).unwrap();
|
|
let env = envelope(b"WAI2", &m, rows);
|
|
let (w, peak, secs) = peak_of(|| WaiMulti::from_bytes(&env).map(|w| w.manifest.renditions.len()));
|
|
assert_eq!(w.expect("read"), rows);
|
|
report("a WAI2 of 65535 renditions", m.len(), values, peak, secs, bound(m.len(), values, rows));
|
|
|
|
let m = manifest(rows + 1);
|
|
let e = WaiMulti::from_bytes(&envelope(b"WAI2", &m, 0)).err().expect("refused").to_string();
|
|
assert!(e.contains("more than the 65535 rows a WAI2 table holds"), "{e}");
|
|
}
|