Auto merge of #9293 - Ms2ger:reftests, r=SimonSapin

Remove the legacy reftest framework.

<!-- Reviewable:start -->
[<img src="https://reviewable.io/review_button.png" height=40 alt="Review on Reviewable"/>](https://reviewable.io/reviews/servo/servo/9293)
<!-- Reviewable:end -->
This commit is contained in:
bors-servo 2016-01-14 15:00:14 +05:30
commit d28fb42201
22 changed files with 228 additions and 556 deletions

View file

@ -201,52 +201,11 @@ class MachCommands(CommandBase):
@Command('test-ref',
description='Run the reference tests',
category='testing')
@CommandArgument('--kind', '-k', default=DEFAULT_RENDER_MODE,
help=HELP_RENDER_MODE)
@CommandArgument('--release', '-r', action='store_true',
help='Run with a release build of Servo')
@CommandArgument('--include', default=None, nargs='+',
help="Only run tests that match this pattern. If the "
"path to the ref test directory is included, it "
"will automatically be trimmed out.")
@CommandArgument(
'servo_params', default=None, nargs=argparse.REMAINDER,
help="Command-line arguments to be passed through to Servo")
def test_ref(self, kind=DEFAULT_RENDER_MODE, include=None, servo_params=None,
release=False):
self.ensure_bootstrapped()
self.ensure_built_tests(release=release)
assert kind is not None, 'kind cannot be None, see help'
kinds = ["cpu", "gpu"] if kind == 'both' else [kind]
test_path = path.join(self.context.topdir, "tests", "ref")
error = False
test_start = time()
for k in kinds:
print("Running %s reftests..." % k)
test_args = [k, test_path]
if include is not None:
ref_path = path.join("tests", "ref")
for name in include:
# Check to see if we were passed something leading with the
# path to the ref test directory, and trim it so that reftest
# knows how to filter it.
maybe_path = path.normpath(name)
if ref_path in maybe_path:
test_args.append(path.relpath(maybe_path, ref_path))
else:
test_args.append(name)
if servo_params is not None:
test_args += ["--"] + servo_params
ret = self.run_test("reftest", test_args, release=release)
error = error or ret != 0
elapsed = time() - test_start
print("Reference tests completed in %0.2fs" % elapsed)
if error:
return 1
@CommandArgument('params', default=None, nargs=argparse.REMAINDER)
def test_ref(self, params=None):
print("Ref tests have been replaced by web-platform-tests under "
"tests/wpt/mozilla/.")
return 0
@Command('test-content',
description='Run the content tests',

View file

@ -19,8 +19,6 @@ import sys
from licenseck import licenses
filetypes_to_check = [".rs", ".rc", ".cpp", ".c", ".h", ".lock", ".py", ".toml", ".webidl"]
reftest_dir = "./tests/ref"
reftest_filetype = ".list"
ignored_files = [
# Upstream
@ -62,10 +60,6 @@ def should_check(file_name):
return True
def should_check_reftest(file_name):
return file_name.endswith(reftest_filetype)
EMACS_HEADER = "/* -*- Mode:"
VIM_HEADER = "/* vim:"
MAX_LICENSE_LINESPAN = max(len(license.splitlines()) for license in licenses)
@ -517,44 +511,6 @@ def collect_errors_for_files(files_to_check, checking_functions, line_checking_f
yield (filename,) + error
def check_reftest_order(files_to_check):
for file_name in files_to_check:
with open(file_name, "r") as fp:
split_lines = fp.read().splitlines()
lines = filter(lambda l: len(l) > 0 and l[0] != '#', split_lines)
for idx, line in enumerate(lines[:-1]):
next_line = lines[idx + 1]
current = get_reftest_names(line)
next = get_reftest_names(next_line)
if current is not None and next is not None and current > next:
yield (file_name, split_lines.index(next_line) + 1, "line not in alphabetical order")
def get_reftest_names(line):
tokens = line.split()
if len(tokens) == 3:
return tokens[1] + tokens[2]
if len(tokens) == 4:
return tokens[2] + tokens[3]
return None
def get_html_file_names_from_reftest_list(reftest_dir, file_name):
for line in open(os.path.join(reftest_dir, file_name), "r"):
for token in line.split():
if fnmatch.fnmatch(token, '*.html'):
yield os.path.join(reftest_dir, token)
def check_reftest_html_files_in_basic_list(reftest_dir):
basic_list_files = set(get_html_file_names_from_reftest_list(reftest_dir, "basic" + reftest_filetype))
for file_name in os.listdir(reftest_dir):
file_path = os.path.join(reftest_dir, file_name)
if fnmatch.fnmatch(file_path, '*.html') and file_path not in basic_list_files:
yield (file_path, "", "not found in basic.list")
def check_wpt_lint_errors():
wpt_working_dir = os.path.abspath(os.path.join(".", "tests", "wpt", "web-platform-tests"))
site.addsitedir(wpt_working_dir)
@ -586,11 +542,6 @@ def scan(faster=False):
line_checking_functions = (check_license, check_by_line, check_toml, check_rust, check_spec)
errors = collect_errors_for_files(files_to_check, checking_functions, line_checking_functions)
# reftest checks
reftest_to_check = filter(should_check_reftest, get_file_list(reftest_dir, faster))
r_errors = check_reftest_order(reftest_to_check)
not_found_in_basic_list_errors = check_reftest_html_files_in_basic_list(reftest_dir)
# wpt lint checks
if faster:
print "\033[93mUsing test-tidy \033[01m--faster\033[22m, skipping WPT lint\033[0m"
@ -599,7 +550,7 @@ def scan(faster=False):
wpt_lint_errors = check_wpt_lint_errors()
# collect errors
errors = itertools.chain(errors, r_errors, not_found_in_basic_list_errors, wpt_lint_errors)
errors = itertools.chain(errors, wpt_lint_errors)
error = None
for error in errors:

Binary file not shown.

Before

Width:  |  Height:  |  Size: 215 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 171 B

View file

@ -1,8 +0,0 @@
# This file must be sorted alphabetically.
# Please run `./mach test-tidy` to check your changes.
# Should be == with expected failure:
fragment=top != ../html/acid2.html acid2_ref.html
# This file must be sorted alphabetically.
# Please run `./mach test-tidy` to check your changes.

View file

@ -1,5 +0,0 @@
body {
font-family: 'ahem';
font-size: 100px;
line-height: 1;
}

View file

@ -1,4 +0,0 @@
body {
display: none;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 510 B

View file

@ -1,48 +0,0 @@
/* http://meyerweb.com/eric/tools/css/reset/
v2.0 | 20110126
License: none (public domain)
*/
html, body, div, span, applet, object, iframe,
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
a, abbr, acronym, address, big, cite, code,
del, dfn, em, img, ins, kbd, q, s, samp,
small, strike, strong, sub, sup, tt, var,
b, u, i, center,
dl, dt, dd, ol, ul, li,
fieldset, form, label, legend,
table, caption, tbody, tfoot, thead, tr, th, td,
article, aside, canvas, details, embed,
figure, figcaption, footer, header, hgroup,
menu, nav, output, ruby, section, summary,
time, mark, audio, video {
margin: 0;
padding: 0;
border: 0;
font-size: 100%;
font: inherit;
vertical-align: baseline;
}
/* HTML5 display-role reset for older browsers */
article, aside, details, figcaption, figure,
footer, header, hgroup, menu, nav, section {
display: block;
}
body {
line-height: 1;
}
ol, ul {
list-style: none;
}
blockquote, q {
quotes: none;
}
blockquote:before, blockquote:after,
q:before, q:after {
content: '';
content: none;
}
table {
border-collapse: collapse;
border-spacing: 0;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 253 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 86 KiB

395
tests/reftest.rs vendored
View file

@ -1,395 +0,0 @@
// Copyright 2013 The Servo Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
#![feature(fs_walk)]
#![feature(slice_patterns)]
#![feature(test)]
#[macro_use] extern crate bitflags;
extern crate image;
extern crate test;
extern crate url;
extern crate util;
use image::{DynamicImage, GenericImage, ImageFormat, RgbImage};
use std::env;
use std::ffi::OsStr;
use std::fs::{File, walk_dir};
use std::io::{self, Read, Result, Write};
use std::path::{Path, PathBuf};
use std::process;
use std::process::{Command};
use std::thread;
use std::time::Duration;
use test::run_tests_console;
use test::{AutoColor, DynTestName, DynTestFn, TestDesc, TestOpts, TestDescAndFn, ShouldPanic};
use url::Url;
bitflags!(
flags RenderMode: u32 {
const CPU_RENDERING = 0x00000001,
const GPU_RENDERING = 0x00000010,
const LINUX_TARGET = 0x00000100,
const MACOS_TARGET = 0x00001000,
const ANDROID_TARGET = 0x00010000
}
);
fn main() {
let args: Vec<String> = env::args().collect();
let mut parts = args[1..].split(|e| &**e == "--");
let harness_args = parts.next().unwrap(); // .split() is never empty
let servo_args = parts.next().unwrap_or(&[]);
let (render_mode_string, base_path, testnames) = match harness_args {
[ref render_mode_string, ref base_path, testnames..] =>
(render_mode_string, base_path, testnames),
_ => panic!("USAGE: cpu|gpu base_path [testname ...]"),
};
let mut render_mode = match &**render_mode_string {
"cpu" => CPU_RENDERING,
"gpu" => GPU_RENDERING,
_ => panic!("First argument must specify cpu or gpu as rendering mode")
};
if cfg!(target_os = "linux") {
render_mode.insert(LINUX_TARGET);
}
if cfg!(target_os = "macos") {
render_mode.insert(MACOS_TARGET);
}
if cfg!(target_os = "android") {
render_mode.insert(ANDROID_TARGET);
}
let mut all_tests = vec!();
println!("Scanning {} for manifests\n", base_path);
for file in walk_dir(base_path).unwrap() {
let file = file.unwrap().path();
let maybe_extension = file.extension();
match maybe_extension {
Some(extension) => {
if extension == OsStr::new("list") && file.is_file() {
let len = all_tests.len();
let mut tests = parse_lists(&file, testnames, servo_args, render_mode, len);
println!("\t{} [{} tests]", file.display(), tests.len());
all_tests.append(&mut tests);
}
}
_ => {}
}
}
let test_opts = TestOpts {
filter: None,
run_ignored: false,
logfile: None,
run_tests: true,
bench_benchmarks: false,
nocapture: false,
color: AutoColor,
};
match run(test_opts,
all_tests,
servo_args.iter().cloned().collect()) {
Ok(false) => process::exit(1), // tests failed
Err(_) => process::exit(2), // I/O-related failure
_ => (),
}
}
fn run(test_opts: TestOpts, all_tests: Vec<TestDescAndFn>,
servo_args: Vec<String>) -> io::Result<bool> {
// Verify that we're passing in valid servo arguments. Otherwise, servo
// will exit before we've run any tests, and it will appear to us as if
// all the tests are failing.
let mut command = Command::new(&servo_path());
command
.args(&servo_args)
.arg("-z")
.arg("about:blank");
let mut child = match command.spawn() {
Ok(p) => p,
Err(e) => panic!("failed to execute process: {}", e),
};
// Wait for the shell to launch or to fail
thread::sleep(Duration::from_secs(1));
child.kill().unwrap();
let output = try!(child.wait_with_output());
let stderr = String::from_utf8(output.stderr).unwrap();
if stderr.contains("Unrecognized") {
println!("Servo: {}", stderr);
return Ok(false);
}
run_tests_console(&test_opts, all_tests)
}
#[derive(PartialEq)]
enum ReftestKind {
Same,
Different,
}
struct Reftest {
name: String,
kind: ReftestKind,
files: [PathBuf; 2],
id: usize,
servo_args: Vec<String>,
render_mode: RenderMode,
is_flaky: bool,
prefs: Vec<String>,
fragment_identifier: Option<String>,
resolution: Option<String>,
pixel_ratio: Option<f32>,
}
struct TestLine<'a> {
conditions: &'a str,
kind: &'a str,
file_left: &'a str,
file_right: &'a str,
}
fn parse_lists(file: &Path,
filters: &[String],
servo_args: &[String],
render_mode: RenderMode,
id_offset: usize)
-> Vec<TestDescAndFn> {
let mut tests = Vec::new();
let contents = {
let mut f = File::open(file).unwrap();
let mut contents = String::new();
f.read_to_string(&mut contents).unwrap();
contents
};
for line in contents.lines() {
// ignore comments or empty lines
if line.starts_with("#") || line.is_empty() {
continue;
}
let parts: Vec<&str> = line.split(' ').filter(|p| !p.is_empty()).collect();
let test_line = match parts.len() {
3 => TestLine {
conditions: "",
kind: parts[0],
file_left: parts[1],
file_right: parts[2],
},
4 => TestLine {
conditions: parts[0],
kind: parts[1],
file_left: parts[2],
file_right: parts[3],
},
_ => panic!("reftest line: '{}' doesn't match '[CONDITIONS] KIND LEFT RIGHT'", line),
};
let kind = match test_line.kind {
"==" => ReftestKind::Same,
"!=" => ReftestKind::Different,
part => panic!("reftest line: '{}' has invalid kind '{}'", line, part)
};
let base = env::current_dir().unwrap().join(file.parent().unwrap());
let file_left = base.join(test_line.file_left);
let file_right = base.join(test_line.file_right);
let conditions_list = test_line.conditions.split(',');
let mut flakiness = RenderMode::empty();
let mut prefs = vec![];
let mut fragment_identifier = None;
let mut resolution = None;
let mut pixel_ratio = None;
for condition in conditions_list {
match condition {
"flaky_cpu" => flakiness.insert(CPU_RENDERING),
"flaky_gpu" => flakiness.insert(GPU_RENDERING),
"flaky_linux" => flakiness.insert(LINUX_TARGET),
"flaky_macos" => flakiness.insert(MACOS_TARGET),
_ => ()
}
if condition.starts_with("prefs:\"") {
if let Some(joined) = condition.split("\"").nth(1) {
prefs.extend(joined.split(",").map(str::to_owned));
}
}
if condition.starts_with("fragment=") {
fragment_identifier = Some(condition["fragment=".len()..].to_string());
}
if condition.starts_with("resolution=") {
resolution = Some(condition["resolution=".len() ..].to_string());
}
if condition.starts_with("device-pixel-ratio=") {
pixel_ratio = Some(condition["device-pixel-ratio=".len() ..].to_string()
.parse().expect("Invalid device-pixel-ratio"));
}
}
let reftest = Reftest {
name: format!("{} {} {}", test_line.file_left, test_line.kind, test_line.file_right),
kind: kind,
files: [file_left, file_right],
id: id_offset + tests.len(),
render_mode: render_mode,
servo_args: servo_args.to_vec(),
is_flaky: render_mode.intersects(flakiness),
prefs: prefs,
fragment_identifier: fragment_identifier,
resolution: resolution,
pixel_ratio: pixel_ratio,
};
if filters.is_empty() || filters.iter().any(|pattern| reftest.name.contains(pattern)) {
tests.push(make_test(reftest));
}
}
tests
}
fn make_test(reftest: Reftest) -> TestDescAndFn {
let name = reftest.name.clone();
TestDescAndFn {
desc: TestDesc {
name: DynTestName(name),
ignore: false,
should_panic: ShouldPanic::No,
},
testfn: DynTestFn(Box::new(move || {
check_reftest(reftest);
})),
}
}
fn capture(reftest: &Reftest, side: usize) -> (u32, u32, Vec<u8>) {
let png_filename = format!("/tmp/servo-reftest-{:06}-{}.png", reftest.id, side);
let mut command = Command::new(&servo_path());
command
.args(&reftest.servo_args[..])
.arg("--user-stylesheet").arg(util::resource_files::resources_dir_path().join("ahem.css"))
// Allows pixel perfect rendering of Ahem font and the HTML canvas for reftests.
.arg("-Z")
.arg("disable-text-aa,disable-canvas-aa")
.args(&["-f", "-o"])
.arg(&png_filename)
.arg(&{
let mut url = Url::from_file_path(&*reftest.files[side]).unwrap();
url.fragment = reftest.fragment_identifier.clone();
url.to_string()
});
// CPU rendering is the default
if reftest.render_mode.contains(CPU_RENDERING) {
command.arg("-c");
}
if reftest.render_mode.contains(GPU_RENDERING) {
command.arg("-g");
}
for pref in &reftest.prefs {
command.arg("--pref");
command.arg(pref);
}
if let Some(ref resolution) = reftest.resolution {
command.arg("--resolution");
command.arg(resolution);
}
if let Some(pixel_ratio) = reftest.pixel_ratio {
command.arg("--device-pixel-ratio");
command.arg(pixel_ratio.to_string());
}
let (exit_status, stderr, stdout) = match command.output() {
Ok(output) => (output.status, output.stderr, output.stdout),
Err(e) => panic!("failed to execute process: {}", e),
};
if !stdout.is_empty() {
let stdout_filename = format!("/tmp/servo-reftest-{:06}-{}-stdout.txt", reftest.id, side);
let mut stdout_file = File::create(stdout_filename).unwrap();
stdout_file.write_all(&stdout[..]).unwrap();
}
if !stderr.is_empty() {
let stderr_filename = format!("/tmp/servo-reftest-{:06}-{}-stderr.txt", reftest.id, side);
let mut stderr_file = File::create(stderr_filename).unwrap();
stderr_file.write_all(&stderr[..]).unwrap();
}
assert!(exit_status.success());
let image = match image::open(&png_filename) {
Ok(DynamicImage::ImageRgb8(image)) => image,
Ok(image) => image.to_rgb(),
_ => panic!(),
};
(image.width(), image.height(), image.into_raw())
}
fn servo_path() -> PathBuf {
let current_exe = env::current_exe().ok().expect("Could not locate current executable");
current_exe.parent().unwrap().join("servo")
}
fn check_reftest(reftest: Reftest) {
let (left_width, left_height, left_bytes) = capture(&reftest, 0);
let (right_width, right_height, right_bytes) = capture(&reftest, 1);
// TODO(gw): This is a workaround for https://github.com/servo/servo/issues/7730
if !reftest.is_flaky {
assert_eq!(left_width, right_width);
assert_eq!(left_height, right_height);
let left_all_white = left_bytes.iter().all(|&p| p == 255);
let right_all_white = right_bytes.iter().all(|&p| p == 255);
if left_all_white && right_all_white {
panic!("Both renderings are empty")
}
}
let pixels = left_bytes.iter().zip(right_bytes.iter()).map(|(&a, &b)| {
if a == b {
// White for correct
0xFF
} else {
// "1100" in the RGBA channel with an error for an incorrect value
// This results in some number of C0 and FFs, which is much more
// readable (and distinguishable) than the previous difference-wise
// scaling but does not require reconstructing the actual RGBA pixel.
0xC0
}
}).collect::<Vec<u8>>();
if pixels.iter().any(|&a| a < 255) {
let output = format!("/tmp/servo-reftest-{:06}-diff.png", reftest.id);
let mut file = File::create(&output).unwrap();
let img_buf = RgbImage::from_raw(left_width, left_height, pixels).expect("foo");
DynamicImage::ImageRgb8(img_buf).save(&mut file, ImageFormat::PNG).unwrap();
match (reftest.kind, reftest.is_flaky) {
(ReftestKind::Same, true) => println!("flaky test - rendering difference: {}", output),
(ReftestKind::Same, false) => panic!("rendering difference: {}", output),
(ReftestKind::Different, _) => {} // Result was different and that's what was expected
}
} else {
assert!(reftest.is_flaky || reftest.kind == ReftestKind::Same);
}
}

View file

@ -151,6 +151,19 @@
"url": "/_mozilla/css/acid1_a.html"
}
],
"css/acid2-wrapper.html": [
{
"path": "css/acid2-wrapper.html",
"references": [
[
"/_mozilla/css/acid2_ref_broken.html",
"=="
]
],
"timeout": "long",
"url": "/_mozilla/css/acid2-wrapper.html"
}
],
"css/acid2_noscroll.html": [
{
"path": "css/acid2_noscroll.html",
@ -163,6 +176,18 @@
"url": "/_mozilla/css/acid2_noscroll.html"
}
],
"css/acid2_ref.html": [
{
"path": "css/acid2_ref.html",
"references": [
[
"/_mozilla/css/acid2_ref_broken.html",
"=="
]
],
"url": "/_mozilla/css/acid2_ref.html"
}
],
"css/after_block_iteration.html": [
{
"path": "css/after_block_iteration.html",
@ -6194,6 +6219,19 @@
"url": "/_mozilla/css/acid1_a.html"
}
],
"css/acid2-wrapper.html": [
{
"path": "css/acid2-wrapper.html",
"references": [
[
"/_mozilla/css/acid2_ref_broken.html",
"=="
]
],
"timeout": "long",
"url": "/_mozilla/css/acid2-wrapper.html"
}
],
"css/acid2_noscroll.html": [
{
"path": "css/acid2_noscroll.html",
@ -6206,6 +6244,18 @@
"url": "/_mozilla/css/acid2_noscroll.html"
}
],
"css/acid2_ref.html": [
{
"path": "css/acid2_ref.html",
"references": [
[
"/_mozilla/css/acid2_ref_broken.html",
"=="
]
],
"url": "/_mozilla/css/acid2_ref.html"
}
],
"css/after_block_iteration.html": [
{
"path": "css/after_block_iteration.html",

View file

@ -0,0 +1,3 @@
[acid2-wrapper.html]
type: reftest
disabled: https://github.com/servo/servo/issues/9310

View file

@ -0,0 +1,3 @@
[acid2_ref.html]
type: reftest
expected: FAIL

View file

@ -0,0 +1,15 @@
<!doctype html>
<html class=reftest-wait>
<meta name=timeout content=long>
<link rel=match href=acid2_ref_broken.html>
<style>
body {
margin: 0;
}
iframe {
width: 100vw;
height: 100vh;
border: 0;
}
</style>
<iframe src="acid2.html#top" onload="document.documentElement.className = ''"></iframe>

View file

@ -0,0 +1,150 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html>
<head>
<title>The Second Acid Test</title>
<style type="text/css">
/* section numbers refer to CSS2.1 */
/* page setup */
html { font: 12px sans-serif; margin: 0; padding: 0; overflow: hidden; /* hides scrollbars on viewport, see 11.1.1:3 */ background: white; color: red; }
body { margin: 0; padding: 0; }
/* introduction message */
.intro { font: 2em sans-serif; margin: 3.5em 2em; padding: 0.5em; border: solid thin; background: white; color: black; position: relative; z-index: 2; /* should cover the black and red bars that are fixed-positioned */ }
.intro * { font: inherit; margin: 0; padding: 0; }
.intro h1 { font-size: 1em; font-weight: bolder; margin: 0; padding: 0; }
.intro :link { color: blue; }
.intro :visited { color: purple; }
/* picture setup */
#top { margin: 100em 3em 0; padding: 2em 0 0 .5em; text-align: left; font: 2em/24px sans-serif; color: navy; white-space: pre; } /* "Hello World!" text */
.picture { position: relative; border: 1em solid transparent; margin: 0 0 100em 3em; } /* containing block for face */
.picture { background: red; } /* overridden by preferred stylesheet below */
/* top line of face (scalp): fixed positioning and min/max height/width */
.picture p { position: fixed; margin: 0; padding: 0; border: 0; top: 9em; left: 11em; width: 140%; max-width: 4em; height: 8px; min-height: 1em; max-height: 2mm; /* min-height overrides max-height, see 10.7 */ background: black; border-bottom: 0.5em yellow solid; }
/* bits that shouldn't be part of the top line (and shouldn't be visible at all): HTML parsing, "+" combinator, stacking order */
.picture p.bad { border-bottom: red solid; /* shouldn't matter, because the "p + table + p" rule below should match it too, thus hiding it */ }
.picture p + p { background: maroon; z-index: 1; } /* shouldn't match anything */
.picture p + table + p { margin-top: 3em; /* should end up under the absolutely positioned table below, and thus not be visible */ }
/* second line of face: attribute selectors, float positioning */
[class~=one].first.one { position: absolute; top: 0; margin: 36px 0 0 60px; padding: 0; border: black 2em; border-style: none solid; /* shrink wraps around float */ }
[class~=one][class~=first] [class=second\ two][class="second two"] { float: right; width: 48px; height: 12px; background: yellow; margin: 0; padding: 0; } /* only content of abs pos block */
/* third line of face: width and overflow */
.forehead { margin: 4em; width: 8em; border-left: solid black 1em; border-right: solid black 1em; background: red url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAADElEQVR42mP4%2F58BAAT%2FAf9jgNErAAAAAElFTkSuQmCC); /* that's a 1x1 yellow pixel PNG */ }
.forehead * { width: 12em; line-height: 1em; }
/* class selectors headache */
.two.error.two { background: maroon; } /* shouldn't match */
.forehead.error.forehead { background: red; } /* shouldn't match */
[class=second two] { background: red; } /* this should be ignored (invalid selector -- grammar says it only accepts IDENTs or STRINGs) */
/* fourth and fifth lines of face, with eyes: paint order test (see appendix E) and fixed backgrounds */
/* the two images are identical: 2-by-2 squares with the top left
and bottom right pixels set to yellow and the other two set to
transparent. Since they are offset by one pixel from each other,
the second one paints exactly over the transparent parts of the
first one, thus creating a solid yellow block. */
.eyes { position: absolute; top: 5em; left: 3em; margin: 0; padding: 0; background: red; }
#eyes-a { height: 0; line-height: 2em; text-align: right; } /* contents should paint top-most because they're inline */
#eyes-a object { display: inline; vertical-align: bottom; }
#eyes-a object[type] { width: 7.5em; height: 2.5em; } /* should have no effect since that object should fallback to being inline (height/width don't apply to inlines) */
#eyes-a object object object { border-right: solid 1em black; padding: 0 12px 0 11px; background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAIAAAD91JpzAAAABnRSTlMAAAAAAABupgeRAAAABmJLR0QA%2FwD%2FAP%2BgvaeTAAAAEUlEQVR42mP4%2F58BCv7%2FZwAAHfAD%2FabwPj4AAAAASUVORK5CYII%3D) fixed 1px 0; }
#eyes-b { float: left; width: 10em; height: 2em; background: fixed url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAIAAAD91JpzAAAABnRSTlMAAAAAAABupgeRAAAABmJLR0QA%2FwD%2FAP%2BgvaeTAAAAEUlEQVR42mP4%2F58BCv7%2FZwAAHfAD%2FabwPj4AAAAASUVORK5CYII%3D); border-left: solid 1em black; border-right: solid 1em red; } /* should paint in the middle layer because it is a float */
#eyes-c { display: block; background: red; border-left: 2em solid yellow; width: 10em; height: 2em; } /* should paint bottom most because it is a block */
/* lines six to nine, with nose: auto margins */
.nose { float: left; margin: -2em 2em -1em; border: solid 1em black; border-top: 0; min-height: 80%; height: 60%; max-height: 3em; /* percentages become auto (see 10.5 and 10.7) and intrinsic height is more than 3em, so 3em wins */ padding: 0; width: 12em; }
.nose > div { padding: 1em 1em 3em; height: 0; background: yellow; }
.nose div div { width: 2em; height: 2em; background: red; margin: auto; }
.nose :hover div { border-color: blue; }
.nose div:hover :before { border-bottom-color: inherit; }
.nose div:hover :after { border-top-color: inherit; }
.nose div div:before { display: block; border-style: none solid solid; border-color: red yellow black yellow; border-width: 1em; content: ''; height: 0; }
.nose div :after { display: block; border-style: solid solid none; border-color: black yellow red yellow; border-width: 1em; content: ''; height: 0; }
/* between lines nine and ten: margin collapsing with 'float' and 'clear' */
.empty { margin: 6.25em; height: 10%; /* computes to auto which makes it empty per 8.3.1:7 (own margins) */ }
.empty div { margin: 0 2em -6em 4em; }
.smile { margin: 5em 3em; clear: both; /* clearance is negative (see 8.3.1 and 9.5.1) */ }
/* line ten and eleven: containing block for abs pos */
.smile div { margin-top: 0.25em; background: black; width: 12em; height: 2em; position: relative; bottom: -1em; }
.smile div div { position: absolute; top: 0; right: 1em; width: auto; height: 0; margin: 0; border: yellow solid 1em; }
/* smile (over lines ten and eleven): backgrounds behind borders, inheritance of 'float', nested floats, negative heights */
.smile div div span { display: inline; margin: -1em 0 0 0; border: solid 1em transparent; border-style: none solid; float: right; background: black; height: 1em; }
.smile div div span em { float: inherit; border-top: solid yellow 1em; border-bottom: solid black 1em; } /* zero-height block; width comes from (zero-height) child. */
.smile div div span em strong { width: 6em; display: block; margin-bottom: -1em; /* should have no effect, since parent has top&bottom borders, so this margin doesn't collapse */ }
/* line twelve: line-height */
.chin { margin: -4em 4em 0; width: 8em; line-height: 1em; border-left: solid 1em black; border-right: solid 1em black; background: yellow url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAIAAAFSDNYfAAAAaklEQVR42u3XQQrAIAwAQeP%2F%2F6wf8CJBJTK9lnQ7FpHGaOurt1I34nfH9pMMZAZ8BwMGEvvh%2BBsJCAgICLwIOA8EBAQEBAQEBAQEBK79H5RfIQAAAAAAAAAAAAAAAAAAAAAAAAAAAID%2FABMSqAfj%2FsLmvAAAAABJRU5ErkJggg%3D%3D) /* 64x64 red square */ no-repeat fixed /* shouldn't be visible unless the smiley is moved to the top left of the viewport */; }
.chin div { display: inline; font: 2px/4px serif; }
/* line thirteen: cascade and selector tests */
.parser-container div { color: maroon; border: solid; color: orange; } /* setup */
div.parser-container * { border-color: black; /* overrides (implied) border-color on previous line */ } /* setup */
* div.parser { border-width: 0 2em; /* overrides (implied) declarations on earlier line */ } /* setup */
/* line thirteen continued: parser tests */
.parser { /* comment parsing test -- comment ends before the end of this line, the backslash should have no effect: \*/ }
.parser { margin: 0 5em 1em; padding: 0 1em; width: 2em; height: 1em; error: \}; background: yellow; } /* setup with parsing test */
* html .parser { background: gray; }
\.parser { padding: 2em; }
.parser { m\argin: 2em; };
.parser { height: 3em; }
.parser { width: 200; }
.parser { border: 5em solid red ! error; }
.parser { background: red pink; }
/* line fourteen (last line of face): table */
ul { display: table; padding: 0; margin: -1em 7em 0; background: red; }
ul li { padding: 0; margin: 0; }
ul li.first-part { display: table-cell; height: 1em; width: 1em; background: black; }
ul li.second-part { display: table; height: 1em; width: 1em; background: black; } /* anonymous table cell wraps around this */
ul li.third-part { display: table-cell; height: 0.5em; /* gets stretched to fit row */ width: 1em; background: black; }
ul li.fourth-part { list-style: none; height: 1em; width: 1em; background: black; } /* anonymous table cell wraps around this */
/* bits that shouldn't appear: inline alignment in cells */
.image-height-test { height: 10px; overflow: hidden; font: 20em serif; } /* only the area between the top of the line box and the top of the image should be visible */
table { margin: 0; border-spacing: 0; }
td { padding: 0; }
</style>
<link rel="appendix stylesheet" href="data:text/css,.picture%20%7B%20background%3A%20none%3B%20%7D"> <!-- this stylesheet should be applied by default -->
</head>
<body>
<div class="intro">
<h1>Standards compliant?</h1>
<p><a href="#top">Take The Acid2 Test</a> and compare it to <a
href="reference.html">the reference rendering</a>.</p>
</div>
<h2 id="top">Hello World!</h2>
<div class="picture">
<p><table><tr><td></table><p class="bad"> <!-- <table> closes <p> per the HTML4 DTD -->
<blockquote class="first one"><address class="second two"></address></blockquote>
<div class="forehead"><div>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</div></div>
<div class="eyes"><div id="eyes-a"><object data="data:application/x-unknown,ERROR"><object data="http://www.damowmow.com/404/" type="text/html"><object data="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGAAAAAYCAYAAAFy7sgCAAAGsUlEQVRo3u2ZbWwcZxHHf3s%2B7LNbO3ZjXBtowprGODRX0qpNQCjmJKuVKhMl1P2AkCwhFOIKkCBSm9IXavGFKAixIAECwkmWo5MrhRI3Ub40IEwQgp6aIDg3Cd6eEqyIHEteah%2B1E69vhw%2BZtTaX8704ZzkKjHS6271nZ56ZZ%2BY%2F%2F%2BdZKF%2FCwYshx3EkkggLsD1v4FQkEZZYLCbAKyG9%2Ba9EIsG6hnUAf8x74K3aUC3j4%2BM54HcsR2oAIomwZOezkv%2FnSHpYNh%2BNCmAE7xv94zvFdd1bHsjMZmQkPSxAJP%2B%2FfuBLwK54PC7JZFKAVJmzXLBt2w%2FMvcDLwIb8QS8CeJ4nkURYIomw7J%2FYJ8BvSiiXptGGxWds2%2Fa9%2Bnaxh%2BYAD%2Bgt04NDgABTpQY2cvvSFLzw86gWeBVwC8SzlOSv2YeBPfmDBoBHgKmR9LBEEmHZfDTqGykqfkUE0nA78BzQGfSgUeP3wNeTXwXg7MwZDhw4UHL6ra2ti79%2FOvljgG8AZ4H64Lhm4MvAocxsRppGG%2FxcXihlwLIs6R%2FfKV2HO%2F26uA94pdDYUKUZUU7W1RQYXA98Gnhaf5%2FXWX0HeAHYoQonqa4sZSOsSWMCWeC9Yko%2BCQwBe4E6oNc0Tc91XTl1%2BaTsn9gnI%2Blhyc5nZWxsrBIkKSbl2tiic3tW53YDEwOKaoFBrcOfqKee53lG9xsPMjV784r%2F4lO%2FpPvyJ9iyZcuvFSaXK5XYeAZ4CDgGvB3MS4B54LQuWYPeuy4iRFsevsXqpuYoqVQKIH2bK1CuDQNo11o4XUzh%2FcDWYIe1LEtyuZx4niee54njOGKapgfsqlL%2Bl2OjEXg8nxrc1dJ0h3hbtL%2BGCtz7KPBF4CuBe9uB15VafE8hr9qylI3HgG8C2%2FK7VyHZoJj7MrBRm30qFotJMpkU27YlHo%2F7Ha5a%2BV%2FKRkSJ4KuKRLVLKapTjB1SzAVIjY2NSXY%2BKyPpYdk%2FsU9OXT4pruv6BdZbBQfKsVGnvWlIe1VB6VQO8JxC1vZYLCbZ%2BaxsPhpdZDyRRFhG0sPiOE6ldKBg2lRg4xF1YCDIIIKN7DGgD3gH%2BBXwejKZfPrs2tPs%2FvPN2bKuYR1nd7xLKBSSJeqoXKnERjPwNWAG%2BLn2rZuM%2B4Tpml6vaWlp4eLcxVusZq5lCgVgOVKJjRqdX86ffL4D5wIoZACnTpw4wRMdT96i%2FImOJxERAs4uVyqxUacF%2FPdiCj%2BjdRBRGFtwXVdG0sPSdbhTmkYbpH98p2RmM2JZlig1vl0GWo4NQ%2Fn%2Bs5pKRXfwjweaxy7TND3HcRZbfC6X8xVPVQlGy7WxVWlO5XRXFXm6EZmrQuSXYyPE3SiVoEhE6Wyr0u2rumO6zv%2B21AFdQAswC1wCMuUCXCmyWQus103Qg8qlDO0lxwOb%2Fl4FiK3AB3VS%2FuKKLtK%2FgbeAnwG%2FvUODuRw%2FFrR0H1UC75fwu8oJ%2FhFsW5VIG%2FBUgEIN6Y65O4AHu4Ap0zQ9y7LEcZyb9lRBUHQcRyzL8unZVBW5bFWAvAp%2BhDQ2g4F47dUYtlU6obXA54DnVdFLekjUGGifh4AFy7LEdV3xj3X9I66m0QZpGm2QrsOd0j%2B%2BU0bSw5KZzYjrun6HWlAd961i4FfCj0aN1Usau%2Bc1lmuXPFwvAEumUut7tQQvAb%2FXb%2FT0bCAej9cODg7yt%2Bm%2F8q2%2F7OUHZ76PnZ1k2p0mJzlykmPancbOTnL0whHs7CQfb%2B5mx2d3sH79%2BtCRI0c6FeaOr9ICrIQfLvA%2B8BGNXxi4R6HrisJVUWrxAVW2oMFf0Aczim8o3kV6enowDIPjF9%2Fk%2BMU3S3rrjzMMg56eHr%2BxP7qKFbASfojG6kpeDGs1tiW53RxwWT%2Bin5q8w4xpQK5evQpAR30H7ZH2khNvj7TTUd8BgD4rqmu1ZKX8qNeY%2BfHz4zlXDgT5E8tpCTUq7XSBC4Euv8227TV9fX1E73%2BYtvo27BmbS9cvFVTY3bSRFza9yOcf6Gfmygy7d%2B%2Fm%2FPnzF4DvrsBLhnJlJfwIKXxv1PheAE4qK6p4H9AGbNKTuhngBPBPXYRe4IemaT5kWZbR19fHNbmGnZ1k4r3U4glDR30Hm5qjbGjsImJEOHbsGHv27JFz5869o0eFq01Jq%2BmHAXwI6FFKagMTgHM7GzFDS%2BoeLSMv7zjzC9x4Y7gxFovVDAwMEI1GaWlpWSzRVCrFwYMH%2FXfxZ4AfAa8B%2F7lDaGg1%2FQgp43lfK0yqtRMuJa3ceKe5DfgYsCYAZ2ngD8CfAkzqTpW7xY%2F%2FSznyX%2FVeUb2kVmX4AAAAAElFTkSuQmCC">ERROR</object></object></object></div><div id="eyes-b"></div><div id="eyes-c"></div></div> <!-- that's a PNG with 8bit alpha containing two eyes -->
<div class="nose"><div><div></div></div></div>
<div class="empty"><div></div></div>
<div class="smile"><div><div><span><em><strong></strong></em></span></div></div></div>
<div class="chin"><div>&nbsp;</div></div>
<div class="parser-container"><div class="parser"><!-- ->ERROR<!- --></div></div> <!-- two dashes is what delimits a comment, so the text "->ERROR<!-" earlier on this line is actually part of a comment -->
<ul>
<li class="first-part"></li>
<li class="second-part"></li>
<li class="third-part"></li>
<li class="fourth-part"></li>
</ul>
<div class="image-height-test"><table><tr><td><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAIAAAFSDNYfAAAAaklEQVR42u3XQQrAIAwAQeP%2F%2F6wf8CJBJTK9lnQ7FpHGaOurt1I34nfH9pMMZAZ8BwMGEvvh%2BBsJCAgICLwIOA8EBAQEBAQEBAQEBK79H5RfIQAAAAAAAAAAAAAAAAAAAAAAAAAAAID%2FABMSqAfj%2FsLmvAAAAABJRU5ErkJggg%3D%3D" alt=""></td></tr></table></div>
</div>
<script>
// window.scroll(0, 2684);
// setInterval(() => { console.log(window.scrollY); window.scroll(0, 2684);
//}, 10);
</script>
</body>
</html>

View file

@ -2,6 +2,7 @@
<html>
<head>
<title>The Second Acid Test (Reference Rendering)</title>
<link rel=match href=acid2_ref_broken.html>
<style type="text/css">
html { margin: 0; padding: 0; border: 0; overflow: hidden; background: white; }
body { margin: 0; padding: 0; border: 0; }

View file

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

Before After
Before After