Files
rust_browser/tests/wpt_harness.rs
Zachary D. Rowitsch 02cf8fd55e Add pixel-based reftest comparison and parallel WPT execution to promote 955 tests
Reftests now fall back to pixel comparison when layout-tree text comparison
fails. Both test and reference HTML are rasterized to 800x600 pixel buffers
and compared per-pixel with a channel tolerance of 2. This handles the
common WPT pattern where tests use different CSS techniques (borders vs
backgrounds) to achieve the same visual result.

Test execution is parallelized using std::thread::scope with progress
reporting every 100 tests. Suite runs in ~11 seconds across all 2,914 tests.

Also fixes missing CDATA stripping in extract_stylesheet_sources() used by
the Pipeline code path.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-14 11:22:16 -05:00

156 lines
5.1 KiB
Rust

//! Web Platform Tests (WPT) harness for a curated HTML/CSS/layout subset.
//!
//! This suite intentionally excludes JavaScript-dependent tests for now.
#[path = "wpt_harness/manifest.rs"]
mod manifest;
#[path = "wpt_harness/runner.rs"]
mod runner;
#[path = "wpt_harness/types.rs"]
mod types;
use manifest::load_manifest;
use runner::{default_artifacts_dir, enforce_case_policy, regenerate_expected_outputs, run_case};
#[allow(unused_imports)]
use types::{WptOutcome, WptStatus};
use std::path::PathBuf;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Mutex;
fn external_wpt_root() -> PathBuf {
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("tests")
.join("external")
.join("wpt")
}
#[test]
fn wpt_suite_matches_manifest_expectations() {
let root = external_wpt_root();
let cases = load_manifest(&root).expect("manifest must parse");
let artifacts_dir = default_artifacts_dir();
let total = cases.len();
let completed = AtomicUsize::new(0);
let passed = AtomicUsize::new(0);
let known_fail = AtomicUsize::new(0);
let pixel_pass = AtomicUsize::new(0);
let failures = Mutex::new(Vec::new());
eprintln!("WPT: running {total} tests across threads...");
std::thread::scope(|s| {
let parallelism = std::thread::available_parallelism()
.map(|n| n.get())
.unwrap_or(4);
let chunk_size = cases.len().div_ceil(parallelism);
for chunk in cases.chunks(chunk_size) {
let artifacts_dir = &artifacts_dir;
let completed = &completed;
let passed = &passed;
let known_fail = &known_fail;
let pixel_pass = &pixel_pass;
let failures = &failures;
s.spawn(move || {
for case in chunk {
let outcome = run_case(case, artifacts_dir);
match (&case.status, &outcome) {
(_, WptOutcome::Pass) => {
if matches!(case.status, WptStatus::KnownFail { .. }) {
pixel_pass.fetch_add(1, Ordering::Relaxed);
}
passed.fetch_add(1, Ordering::Relaxed);
}
(WptStatus::KnownFail { .. }, WptOutcome::Fail { .. }) => {
known_fail.fetch_add(1, Ordering::Relaxed);
}
_ => {}
}
if let Err(err) = enforce_case_policy(case, outcome) {
failures.lock().unwrap().push(format!("{}: {err}", case.id));
}
let done = completed.fetch_add(1, Ordering::Relaxed) + 1;
if done.is_multiple_of(100) || done == total {
eprintln!(
"WPT: [{done}/{total}] pass={} known_fail={} pixel_promoted={}",
passed.load(Ordering::Relaxed),
known_fail.load(Ordering::Relaxed),
pixel_pass.load(Ordering::Relaxed),
);
}
}
});
}
});
let failures = failures.into_inner().unwrap();
eprintln!(
"WPT: complete — pass={} known_fail={} pixel_promoted={} failures={}",
passed.load(Ordering::Relaxed),
known_fail.load(Ordering::Relaxed),
pixel_pass.load(Ordering::Relaxed),
failures.len(),
);
if !failures.is_empty() {
panic!(
"WPT harness failures ({}):\n{}",
failures.len(),
failures.join("\n")
);
}
}
#[test]
fn wpt_manifest_has_nonempty_reasons_for_nonpassing_entries() {
let root = external_wpt_root();
let cases = load_manifest(&root).expect("manifest must parse");
for case in cases {
match case.status {
WptStatus::KnownFail { reason } | WptStatus::Skip { reason } => {
assert!(
!reason.trim().is_empty(),
"case '{}' must include a non-empty reason",
case.id
);
}
WptStatus::Pass => {}
}
}
}
#[test]
fn wpt_runner_is_deterministic_for_reference_case() {
let root = external_wpt_root();
let cases = load_manifest(&root).expect("manifest must parse");
let case = cases
.iter()
.find(|c| c.id == "wpt-css-text-align-center")
.expect("determinism reference case must exist");
let artifacts_dir = default_artifacts_dir();
let first = run_case(case, &artifacts_dir);
let second = run_case(case, &artifacts_dir);
assert_eq!(first, second, "same case must be deterministic");
assert!(matches!(first, WptOutcome::Pass));
}
#[test]
#[ignore]
fn regenerate_wpt_expected_outputs() {
let root = external_wpt_root();
let cases = load_manifest(&root).expect("manifest must parse");
let rewritten = regenerate_expected_outputs(&cases).expect("expected output regeneration");
println!("rewrote {rewritten} WPT expected output files");
}