mirror of
https://github.com/servo/servo.git
synced 2025-08-06 22:15:33 +01:00
Rewrite reftest harness and add basic example reftest.
This commit is contained in:
parent
073f4240aa
commit
728330fb88
5 changed files with 138 additions and 168 deletions
|
@ -22,7 +22,7 @@ servo-test: $(DEPS_servo)
|
||||||
$(RUSTC) $(RFLAGS_servo) --test -o $@ $<
|
$(RUSTC) $(RFLAGS_servo) --test -o $@ $<
|
||||||
|
|
||||||
reftest: $(S)src/test/harness/reftest/reftest.rs servo
|
reftest: $(S)src/test/harness/reftest/reftest.rs servo
|
||||||
$(RUSTC) $(RFLAGS_servo) -o $@ $< -L .
|
$(RUSTC) -o $@ $<
|
||||||
|
|
||||||
contenttest: $(S)src/test/harness/contenttest/contenttest.rs servo
|
contenttest: $(S)src/test/harness/contenttest/contenttest.rs servo
|
||||||
$(RUSTC) $(RFLAGS_servo) -o $@ $< -L .
|
$(RUSTC) $(RFLAGS_servo) -o $@ $< -L .
|
||||||
|
@ -44,15 +44,15 @@ check-all: $(DEPS_CHECK_TARGETS_ALL) check-servo tidy
|
||||||
|
|
||||||
.PHONY: check-servo
|
.PHONY: check-servo
|
||||||
check-servo: servo-test
|
check-servo: servo-test
|
||||||
./servo-test $(TESTNAME)
|
./servo-test
|
||||||
|
|
||||||
.PHONY: check-ref
|
.PHONY: check-ref
|
||||||
check-ref: reftest
|
check-ref: reftest
|
||||||
./reftest --source-dir=$(S)/src/test/html/ref --work-dir=src/test/html/ref $(TESTNAME)
|
./reftest $(S)src/test/ref/*.list
|
||||||
|
|
||||||
.PHONY: check-content
|
.PHONY: check-content
|
||||||
check-content: contenttest
|
check-content: contenttest
|
||||||
./contenttest --source-dir=$(S)/src/test/html/content $(TESTNAME)
|
./contenttest --source-dir=$(S)src/test/html/content $(TESTNAME)
|
||||||
|
|
||||||
.PHONY: tidy
|
.PHONY: tidy
|
||||||
tidy:
|
tidy:
|
||||||
|
|
|
@ -8,188 +8,141 @@
|
||||||
// except according to those terms.
|
// except according to those terms.
|
||||||
|
|
||||||
extern mod std;
|
extern mod std;
|
||||||
extern mod servo;
|
extern mod extra;
|
||||||
|
|
||||||
use std::test::{TestOpts, run_tests_console, TestDesc};
|
use std::cell::Cell;
|
||||||
use std::getopts::{getopts, reqopt, opt_str, fail_str};
|
use std::io;
|
||||||
use os::list_dir_path;
|
use std::os;
|
||||||
use servo::run_pipeline_png;
|
use std::run;
|
||||||
use servo::image::base::Image;
|
use extra::digest::{Digest, DigestUtil};
|
||||||
|
use extra::sha1::Sha1;
|
||||||
|
use extra::test::{DynTestName, DynTestFn, TestDesc, TestOpts, TestDescAndFn};
|
||||||
|
use extra::test::run_tests_console;
|
||||||
|
|
||||||
fn main(args: ~[~str]) {
|
fn main() {
|
||||||
let config = parse_config(args);
|
let args = os::args();
|
||||||
let opts = test_options(config);
|
if args.len() < 2 {
|
||||||
let tests = find_tests(config);
|
println("error: at least one reftest list must be given");
|
||||||
install_rasterize_py(config);
|
os::set_exit_status(1);
|
||||||
run_tests_console(opts, tests);
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
struct Config {
|
|
||||||
source_dir: ~str,
|
|
||||||
work_dir: ~str,
|
|
||||||
filter: Option<~str>
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_config(args: ~[~str]) -> Config {
|
|
||||||
let args = args.tail();
|
|
||||||
let opts = ~[reqopt(~"source-dir"), reqopt(~"work-dir")];
|
|
||||||
let matches = match getopts(args, opts) {
|
|
||||||
Ok(m) => m,
|
|
||||||
Err(f) => fail fail_str(f)
|
|
||||||
};
|
|
||||||
|
|
||||||
Config {
|
|
||||||
source_dir: opt_str(matches, ~"source-dir"),
|
|
||||||
work_dir: opt_str(matches, ~"work-dir"),
|
|
||||||
filter: if matches.free.is_empty() {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some(matches.free.head())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
fn test_options(config: Config) -> TestOpts {
|
let tests = parse_lists(args.tail());
|
||||||
{
|
let test_opts = TestOpts {
|
||||||
filter: config.filter,
|
filter: None,
|
||||||
run_ignored: false,
|
run_ignored: false,
|
||||||
logfile: None
|
logfile: None,
|
||||||
}
|
run_tests: true,
|
||||||
}
|
run_benchmarks: false,
|
||||||
|
save_results: None,
|
||||||
fn find_tests(config: Config) -> ~[TestDesc] {
|
compare_results: None,
|
||||||
let all_files = list_dir_path(&Path(config.source_dir));
|
|
||||||
let html_files = all_files.filter( |file| file.to_str().ends_with(".html") );
|
|
||||||
return html_files.map(|file| make_test(config, (*file).to_str()) );
|
|
||||||
}
|
|
||||||
|
|
||||||
fn make_test(config: Config, file: ~str) -> TestDesc {
|
|
||||||
let directives = load_test_directives(file);
|
|
||||||
|
|
||||||
{
|
|
||||||
name: file,
|
|
||||||
testfn: fn~() { run_test(config, file) },
|
|
||||||
ignore: directives.ignore,
|
|
||||||
should_fail: false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Directives {
|
|
||||||
ignore: bool
|
|
||||||
}
|
|
||||||
|
|
||||||
fn load_test_directives(file: ~str) -> Directives {
|
|
||||||
let data = match io::read_whole_file_str(&Path(file)) {
|
|
||||||
result::Ok(data) => data,
|
|
||||||
result::Err(e) => fail #fmt("unable to load directives for %s: %s", file, e)
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut ignore = false;
|
if !run_tests_console(&test_opts, tests) {
|
||||||
|
os::set_exit_status(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for str::lines(data).each |line| {
|
enum ReftestKind {
|
||||||
if is_comment(line) {
|
Same,
|
||||||
if line.contains("ignore") {
|
Different,
|
||||||
ignore = true;
|
}
|
||||||
break;
|
|
||||||
|
struct Reftest {
|
||||||
|
name: ~str,
|
||||||
|
kind: ReftestKind,
|
||||||
|
left: ~str,
|
||||||
|
right: ~str,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_lists(filenames: &[~str]) -> ~[TestDescAndFn] {
|
||||||
|
let mut tests: ~[TestDescAndFn] = ~[];
|
||||||
|
for filenames.iter().advance |file| {
|
||||||
|
let file_path = Path(*file);
|
||||||
|
let contents = match io::read_whole_file_str(&file_path) {
|
||||||
|
Ok(x) => x,
|
||||||
|
Err(s) => fail!(s)
|
||||||
|
};
|
||||||
|
|
||||||
|
for contents.line_iter().advance |line| {
|
||||||
|
let parts: ~[&str] = line.split_iter(' ').filter(|p| !p.is_empty()).collect();
|
||||||
|
|
||||||
|
if parts.len() != 3 {
|
||||||
|
fail!(fmt!("reftest line: '%s' doesn't match 'KIND LEFT RIGHT'", line));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let kind = match parts[0] {
|
||||||
|
"==" => Same,
|
||||||
|
"!=" => Different,
|
||||||
|
_ => fail!(fmt!("reftest line: '%s' has invalid kind '%s'",
|
||||||
|
line, parts[0]))
|
||||||
|
};
|
||||||
|
let src_dir = file_path.dirname();
|
||||||
|
let file_left = src_dir + "/" + parts[1];
|
||||||
|
let file_right = src_dir + "/" + parts[2];
|
||||||
|
|
||||||
|
let reftest = Reftest {
|
||||||
|
name: parts[1] + " / " + parts[2],
|
||||||
|
kind: kind,
|
||||||
|
left: file_left,
|
||||||
|
right: file_right,
|
||||||
|
};
|
||||||
|
|
||||||
|
tests.push(make_test(reftest));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
tests
|
||||||
|
}
|
||||||
|
|
||||||
fn is_comment(line: ~str) -> bool {
|
fn make_test(reftest: Reftest) -> TestDescAndFn {
|
||||||
line.starts_with("<!--")
|
let name = reftest.name.clone();
|
||||||
}
|
let reftest = Cell::new(reftest);
|
||||||
|
TestDescAndFn {
|
||||||
return Directives {
|
desc: TestDesc {
|
||||||
ignore: ignore
|
name: DynTestName(name),
|
||||||
|
ignore: false,
|
||||||
|
should_fail: false,
|
||||||
|
},
|
||||||
|
testfn: DynTestFn(|| {
|
||||||
|
check_reftest(reftest.take());
|
||||||
|
}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run_test(config: Config, file: ~str) {
|
fn check_reftest(reftest: Reftest) {
|
||||||
let servo_image = render_servo(config, file);
|
let options = run::ProcessOptions::new();
|
||||||
let ref_image = render_ref(config, file);
|
let args = ~[~"-o", ~"/tmp/reftest-left.png", reftest.left.clone()];
|
||||||
|
let mut process = run::Process::new("./servo", args, options);
|
||||||
|
let _retval = process.finish();
|
||||||
|
// assert!(retval == 0);
|
||||||
|
|
||||||
assert servo_image.width == ref_image.width;
|
let args = ~[~"-o", ~"/tmp/reftest-right.png", reftest.right.clone()];
|
||||||
assert servo_image.height == ref_image.height;
|
let mut process = run::Process::new("./servo", args, options);
|
||||||
#debug("image depth: ref: %?, servo: %?", ref_image.depth, servo_image.depth);
|
let _retval = process.finish();
|
||||||
|
// assert!(retval == 0);
|
||||||
|
|
||||||
for uint::range(0, servo_image.height) |h| {
|
// check the pngs are bit equal
|
||||||
for uint::range(0, servo_image.width) |w| {
|
let left_sha = calc_hash(&Path("/tmp/reftest-left.png"));
|
||||||
let i = (h * servo_image.width + w) * 4;
|
let right_sha = calc_hash(&Path("/tmp/reftest-right.png"));
|
||||||
let servo_pixel = (
|
assert!(left_sha.is_some());
|
||||||
servo_image.data[i + 0],
|
assert!(right_sha.is_some());
|
||||||
servo_image.data[i + 1],
|
match reftest.kind {
|
||||||
servo_image.data[i + 2],
|
Same => assert!(left_sha == right_sha),
|
||||||
servo_image.data[i + 3]
|
Different => assert!(left_sha != right_sha),
|
||||||
);
|
}
|
||||||
let ref_pixel = (
|
}
|
||||||
ref_image.data[i + 0],
|
|
||||||
ref_image.data[i + 1],
|
|
||||||
ref_image.data[i + 2],
|
|
||||||
ref_image.data[i + 3]
|
|
||||||
);
|
|
||||||
#debug("i: %?, x: %?, y: %?, ref: %?, servo: %?", i, w, h, ref_pixel, servo_pixel);
|
|
||||||
|
|
||||||
let (sr, sg, sb, sa) = servo_pixel;
|
fn calc_hash(path: &Path) -> Option<~str> {
|
||||||
let (rr, rg, rb, ra) = ref_pixel;
|
match io::file_reader(path) {
|
||||||
|
Err(*) => None,
|
||||||
if sr != rr
|
Ok(reader) => {
|
||||||
|| sg != rg
|
let mut sha = Sha1::new();
|
||||||
|| sb != rb
|
loop {
|
||||||
|| sa != ra {
|
let bytes = reader.read_bytes(4096);
|
||||||
fail #fmt("mismatched pixel. x: %?, y: %?, ref: %?, servo: %?", w, h, ref_pixel, servo_pixel)
|
sha.input(bytes);
|
||||||
|
if bytes.len() < 4096 { break; }
|
||||||
}
|
}
|
||||||
|
Some(sha.result_str())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const WIDTH: uint = 800;
|
|
||||||
const HEIGHT: uint = 600;
|
|
||||||
|
|
||||||
fn render_servo(config: Config, file: ~str) -> Image {
|
|
||||||
let infile = ~"file://" + os::make_absolute(&Path(file)).to_str();
|
|
||||||
let outfilename = Path(file).filename().get().to_str() + ".png";
|
|
||||||
let outfile = Path(config.work_dir).push(outfilename).to_str();
|
|
||||||
run_pipeline_png(infile, outfile);
|
|
||||||
return sanitize_image(outfile);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render_ref(config: Config, file: ~str) -> Image {
|
|
||||||
let infile = file;
|
|
||||||
let outfilename = Path(file).filename().get().to_str() + "ref..png";
|
|
||||||
let outfile = Path(config.work_dir).push(outfilename);
|
|
||||||
// After we've generated the reference image once, we don't need
|
|
||||||
// to keep launching Firefox
|
|
||||||
if !os::path_exists(&outfile) {
|
|
||||||
let rasterize_path = rasterize_path(config);
|
|
||||||
let prog = run::start_program("python", ~[rasterize_path, infile, outfile.to_str()]);
|
|
||||||
prog.finish();
|
|
||||||
}
|
|
||||||
return sanitize_image(outfile.to_str());
|
|
||||||
}
|
|
||||||
|
|
||||||
fn sanitize_image(file: ~str) -> Image {
|
|
||||||
let buf = io::read_whole_file(&Path(file)).get();
|
|
||||||
let image = servo::image::base::load_from_memory(buf).get();
|
|
||||||
|
|
||||||
// I don't know how to precisely control the rendered height of
|
|
||||||
// the Firefox output, so it is larger than we want. Trim it down.
|
|
||||||
assert image.width == WIDTH;
|
|
||||||
assert image.height >= HEIGHT;
|
|
||||||
let data = vec::slice(image.data, 0, image.width * HEIGHT * 4);
|
|
||||||
|
|
||||||
return Image(image.width, HEIGHT, image.depth, data);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn install_rasterize_py(config: Config) {
|
|
||||||
use io::WriterUtil;
|
|
||||||
let path = rasterize_path(config);
|
|
||||||
let writer = io::file_writer(&Path(path), ~[io::Create, io::Truncate]).get();
|
|
||||||
writer.write_str(rasterize_py());
|
|
||||||
}
|
|
||||||
|
|
||||||
fn rasterize_path(config: Config) -> ~str {
|
|
||||||
Path(config.work_dir).push(~"rasterize.py").to_str()
|
|
||||||
}
|
|
||||||
|
|
||||||
// This is the script that uses phantom.js to render pages
|
|
||||||
fn rasterize_py() -> ~str { #include_str("rasterize.py") }
|
|
1
src/test/ref/basic.list
Normal file
1
src/test/ref/basic.list
Normal file
|
@ -0,0 +1 @@
|
||||||
|
== hello_a.html hello_b.html
|
8
src/test/ref/hello_a.html
Normal file
8
src/test/ref/hello_a.html
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>hello</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<strong>Hello!</strong>
|
||||||
|
</body>
|
||||||
|
</html>
|
8
src/test/ref/hello_b.html
Normal file
8
src/test/ref/hello_b.html
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>hello</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<b>Hello!</b>
|
||||||
|
</body>
|
||||||
|
</html>
|
Loading…
Add table
Add a link
Reference in a new issue