Merge pull request #3110 from SimonSapin/writing-modes-reftest

Add a basic CSS Writing Modes reftest
This commit is contained in:
Simon Sapin 2014-08-27 01:07:45 +01:00
commit a0d70c4cfd
13 changed files with 554 additions and 86 deletions

View file

@ -316,7 +316,6 @@ impl DisplayList {
}
}
/// Appends the given item to the display list.
pub fn push(&mut self, item: DisplayItem) {
self.list.push(item)
@ -328,6 +327,14 @@ impl DisplayList {
self.list.append(other.list)
}
pub fn debug(&self) {
if log_enabled!(::log::DEBUG) {
for item in self.list.iter() {
item.debug_with_level(0);
}
}
}
/// Draws the display list into the given render context. The display list must be flattened
/// first for correct painting.
pub fn draw_into_context(&self, render_context: &mut RenderContext) {
@ -724,14 +731,14 @@ impl DisplayItem {
}
pub fn debug_with_level(&self, level: uint) {
let mut indent = String::new();
for _ in range(0, level) {
indent.push_str("| ")
}
debug!("{}+ {}", indent, self);
for child in self.children() {
child.debug_with_level(level + 1);
}
let mut indent = String::new();
for _ in range(0, level) {
indent.push_str("| ")
}
debug!("{}+ {}", indent, self);
for child in self.children() {
child.debug_with_level(level + 1);
}
}
}

View file

@ -1110,16 +1110,18 @@ impl BlockFlow {
.relative_containing_block_size,
None);
// FIXME(#2795): Get the real container size
let container_size = Size2D::zero();
// Add the box that starts the block context.
let mut display_list = DisplayList::new();
let mut accumulator =
self.fragment.build_display_list(&mut display_list,
layout_context,
self.base.abs_position
.add_point(&offset)
+ rel_offset,
background_border_level,
None);
let mut accumulator = self.fragment.build_display_list(
&mut display_list,
layout_context,
self.base.abs_position + (offset + rel_offset).to_physical(
self.base.writing_mode, container_size),
background_border_level,
None);
let mut child_layers = DList::new();
for kid in self.base.child_iter() {
@ -1592,17 +1594,22 @@ impl Flow for BlockFlow {
}
fn compute_absolute_position(&mut self) {
// FIXME(#2795): Get the real container size
let container_size = Size2D::zero();
if self.is_absolutely_positioned() {
let position_start = self.base.position.start.to_physical(
self.base.writing_mode, container_size);
self.base
.absolute_position_info
.absolute_containing_block_position = if self.is_fixed() {
// The viewport is initially at (0, 0).
self.base.position.start
position_start
} else {
// Absolute position of the containing block + position of absolute flow w/r/t the
// containing block.
self.base.absolute_position_info.absolute_containing_block_position
.add_point(&self.base.position.start)
+ position_start
};
// Set the absolute position, which will be passed down later as part
@ -1622,8 +1629,8 @@ impl Flow for BlockFlow {
if self.is_positioned() {
self.base.absolute_position_info.absolute_containing_block_position =
self.base.abs_position
.add_point(&self.generated_containing_block_rect().start)
+ relative_offset
+ (self.generated_containing_block_rect().start
+ relative_offset).to_physical(self.base.writing_mode, container_size)
}
let float_offset = if self.is_float() {
@ -1640,14 +1647,14 @@ impl Flow for BlockFlow {
// Process children.
let this_position = self.base.abs_position;
let writing_mode = self.base.writing_mode;
for kid in self.base.child_iter() {
if !kid.is_absolutely_positioned() {
let kid_base = flow::mut_base(kid);
kid_base.abs_position =
this_position
.add_point(&kid_base.position.start)
kid_base.abs_position = this_position + (
kid_base.position.start
.add_point(&float_offset)
+ relative_offset;
+ relative_offset).to_physical(writing_mode, container_size);
kid_base.absolute_position_info = absolute_position_info
}
}

View file

@ -46,13 +46,15 @@ use table_cell::TableCellFlow;
use wrapper::ThreadSafeLayoutNode;
use collections::dlist::DList;
use geom::Point2D;
use gfx::display_list::DisplayList;
use gfx::render_task::RenderLayer;
use servo_msg::compositor_msg::LayerId;
use servo_util::geometry::Au;
use servo_util::logical_geometry::WritingMode;
use servo_util::logical_geometry::{LogicalPoint, LogicalRect, LogicalSize};
use servo_util::logical_geometry::{LogicalRect, LogicalSize};
use std::mem;
use std::num::Zero;
use std::fmt;
use std::iter::Zip;
use std::sync::atomics::{AtomicUint, Relaxed, SeqCst};
@ -596,7 +598,7 @@ pub struct AbsolutePositionInfo {
/// The size of the containing block for relatively-positioned descendants.
pub relative_containing_block_size: LogicalSize<Au>,
/// The position of the absolute containing block.
pub absolute_containing_block_position: LogicalPoint<Au>,
pub absolute_containing_block_position: Point2D<Au>,
/// Whether the absolute containing block forces positioned descendants to be layerized.
///
/// FIXME(pcwalton): Move into `FlowFlags`.
@ -609,7 +611,7 @@ impl AbsolutePositionInfo {
// of the root layer.
AbsolutePositionInfo {
relative_containing_block_size: LogicalSize::zero(writing_mode),
absolute_containing_block_position: LogicalPoint::zero(writing_mode),
absolute_containing_block_position: Zero::zero(),
layers_needed_for_positioned_flows: false,
}
}
@ -660,7 +662,7 @@ pub struct BaseFlow {
pub collapsible_margins: CollapsibleMargins,
/// The position of this flow in page coordinates, computed during display list construction.
pub abs_position: LogicalPoint<Au>,
pub abs_position: Point2D<Au>,
/// Details about descendants with position 'absolute' or 'fixed' for which we are the
/// containing block. This is in tree order. This includes any direct children.
@ -724,7 +726,7 @@ impl BaseFlow {
floats: Floats::new(writing_mode),
collapsible_margins: CollapsibleMargins::new(),
abs_position: LogicalPoint::zero(writing_mode),
abs_position: Zero::zero(),
abs_descendants: Descendants::new(),
absolute_static_i_offset: Au::new(0),
fixed_static_i_offset: Au::new(0),

View file

@ -37,7 +37,7 @@ use servo_net::image::holder::ImageHolder;
use servo_net::local_image_cache::LocalImageCache;
use servo_util::geometry::Au;
use servo_util::geometry;
use servo_util::logical_geometry::{LogicalPoint, LogicalRect, LogicalSize, LogicalMargin};
use servo_util::logical_geometry::{LogicalRect, LogicalSize, LogicalMargin};
use servo_util::range::*;
use servo_util::namespace;
use servo_util::smallvec::SmallVec;
@ -776,15 +776,15 @@ impl Fragment {
fn build_debug_borders_around_text_fragments(&self,
display_list: &mut DisplayList,
flow_origin: LogicalPoint<Au>,
flow_origin: Point2D<Au>,
text_fragment: &ScannedTextFragmentInfo) {
let mut fragment_bounds = self.border_box.clone();
fragment_bounds.start.i = fragment_bounds.start.i + flow_origin.i;
fragment_bounds.start.b = fragment_bounds.start.b + flow_origin.b;
// FIXME(#2795): Get the real container size
let container_size = Size2D::zero();
let absolute_fragment_bounds = fragment_bounds.to_physical(
self.style.writing_mode, container_size);
// Fragment position wrt to the owning flow.
let fragment_bounds = self.border_box.to_physical(self.style.writing_mode, container_size);
let absolute_fragment_bounds = Rect(
fragment_bounds.origin + flow_origin,
fragment_bounds.size);
// Compute the text fragment bounds and draw a border surrounding them.
let border_display_item = box BorderDisplayItem {
@ -797,13 +797,11 @@ impl Fragment {
// Draw a rectangle representing the baselines.
let ascent = text_fragment.run.ascent();
let baseline = LogicalRect::new(
self.style.writing_mode,
fragment_bounds.start.i,
fragment_bounds.start.b + ascent,
fragment_bounds.size.inline,
Au(0)
).to_physical(self.style.writing_mode, container_size);
let mut baseline = self.border_box.clone();
baseline.start.b = baseline.start.b + ascent;
baseline.size.block = Au(0);
let mut baseline = baseline.to_physical(self.style.writing_mode, container_size);
baseline.origin = baseline.origin + flow_origin;
let line_display_item = box LineDisplayItem {
base: BaseDisplayItem::new(baseline, self.node, ContentStackingLevel),
@ -815,14 +813,14 @@ impl Fragment {
fn build_debug_borders_around_fragment(&self,
display_list: &mut DisplayList,
flow_origin: LogicalPoint<Au>) {
let mut fragment_bounds = self.border_box.clone();
fragment_bounds.start.i = fragment_bounds.start.i + flow_origin.i;
fragment_bounds.start.b = fragment_bounds.start.b + flow_origin.b;
flow_origin: Point2D<Au>) {
// FIXME(#2795): Get the real container size
let container_size = Size2D::zero();
let absolute_fragment_bounds = fragment_bounds.to_physical(
self.style.writing_mode, container_size);
// Fragment position wrt to the owning flow.
let fragment_bounds = self.border_box.to_physical(self.style.writing_mode, container_size);
let absolute_fragment_bounds = Rect(
fragment_bounds.origin + flow_origin,
fragment_bounds.size);
// This prints a debug border around the border of this fragment.
let border_display_item = box BorderDisplayItem {
@ -845,18 +843,17 @@ impl Fragment {
pub fn build_display_list(&self,
display_list: &mut DisplayList,
layout_context: &LayoutContext,
flow_origin: LogicalPoint<Au>,
flow_origin: Point2D<Au>,
background_and_border_level: BackgroundAndBorderLevel,
inline_fragment_context: Option<InlineFragmentContext>)
-> ChildDisplayListAccumulator {
// Fragment position wrt to the owning flow.
let mut fragment_bounds = self.border_box.clone();
fragment_bounds.start.i = fragment_bounds.start.i + flow_origin.i;
fragment_bounds.start.b = fragment_bounds.start.b + flow_origin.b;
// FIXME(#2795): Get the real container size
let container_size = Size2D::zero();
let absolute_fragment_bounds = fragment_bounds.to_physical(
self.style.writing_mode, container_size);
// Fragment position wrt to the owning flow.
let fragment_bounds = self.border_box.to_physical(self.style.writing_mode, container_size);
let absolute_fragment_bounds = Rect(
fragment_bounds.origin + flow_origin,
fragment_bounds.size);
debug!("Fragment::build_display_list at rel={}, abs={}: {}",
self.border_box,
absolute_fragment_bounds,
@ -1413,21 +1410,18 @@ impl Fragment {
#[inline(never)]
fn finalize_position_and_size_of_iframe(&self,
iframe_fragment: &IframeFragmentInfo,
offset: LogicalPoint<Au>,
offset: Point2D<Au>,
layout_context: &LayoutContext) {
let inline_start = offset.i + self.margin.inline_start + self.border_padding.inline_start;
let block_start = offset.b + self.margin.block_start + self.border_padding.block_start;
let inline_size = self.content_box().size.inline;
let block_size = self.content_box().size.block;
// FIXME(#2795): Get the real container size
let container_size = Size2D::zero();
let rect = LogicalRect::new(
self.style.writing_mode,
geometry::to_frac_px(inline_start) as f32,
geometry::to_frac_px(block_start) as f32,
geometry::to_frac_px(inline_size) as f32,
geometry::to_frac_px(block_size) as f32
).to_physical(self.style.writing_mode, container_size);
let mbp = (self.margin + self.border_padding).to_physical(self.style.writing_mode);
let content_size = self.content_box().size.to_physical(self.style.writing_mode);
let left = offset.x + mbp.left;
let top = offset.y + mbp.top;
let width = content_size.width;
let height = content_size.height;
let origin = Point2D(geometry::to_frac_px(left) as f32, geometry::to_frac_px(top) as f32);
let size = Size2D(geometry::to_frac_px(width) as f32, geometry::to_frac_px(height) as f32);
let rect = Rect(origin, size);
debug!("finalizing position and size of iframe for {:?},{:?}",
iframe_fragment.pipeline_id,

View file

@ -16,7 +16,7 @@ use text;
use wrapper::ThreadSafeLayoutNode;
use collections::{Deque, RingBuf};
use geom::Size2D;
use geom::Rect;
use gfx::display_list::ContentLevel;
use gfx::font::FontMetrics;
use gfx::font_context::FontContext;
@ -927,12 +927,8 @@ impl InlineFlow {
}
pub fn build_display_list_inline(&mut self, layout_context: &LayoutContext) {
let abs_rect = LogicalRect::from_point_size(
self.base.writing_mode, self.base.abs_position, self.base.position.size);
// FIXME(#2795): Get the real container size
let container_size = Size2D::zero();
if !abs_rect.to_physical(self.base.writing_mode, container_size)
.intersects(&layout_context.shared.dirty) {
let size = self.base.position.size.to_physical(self.base.writing_mode);
if !Rect(self.base.abs_position, size).intersects(&layout_context.shared.dirty) {
return
}
@ -947,7 +943,8 @@ impl InlineFlow {
Some(context));
drop(fragment.build_display_list(&mut self.base.display_list,
layout_context,
self.base.abs_position + rel_offset,
self.base.abs_position.add_size(
&rel_offset.to_physical(self.base.writing_mode)),
ContentLevel,
Some(context)));
}

View file

@ -46,6 +46,7 @@ use gfx::font_cache_task::{FontCacheTask};
use servo_net::local_image_cache::{ImageResponder, LocalImageCache};
use servo_util::geometry::Au;
use servo_util::geometry;
use servo_util::logical_geometry::LogicalPoint;
use servo_util::opts::Opts;
use servo_util::smallvec::{SmallVec, SmallVec1};
use servo_util::time::{TimeProfilerChan, profile};
@ -694,10 +695,10 @@ impl LayoutTask {
if data.goal == ReflowForDisplay {
let writing_mode = flow::base(layout_root.get()).writing_mode;
profile(time::LayoutDispListBuildCategory, self.time_profiler_chan.clone(), || {
// FIXME(#2795): Get the real container size
let container_size = Size2D::zero();
shared_layout_ctx.dirty = flow::base(layout_root.get()).position.to_physical(
writing_mode, container_size);
writing_mode, self.screen_size);
flow::mut_base(layout_root.get_mut()).abs_position =
LogicalPoint::zero(writing_mode).to_physical(writing_mode, self.screen_size);
match self.parallel_traversal {
None => {
@ -718,6 +719,7 @@ impl LayoutTask {
let root_display_list =
mem::replace(&mut flow::mut_base(layout_root.get_mut()).display_list,
DisplayList::new());
root_display_list.debug();
let display_list = Arc::new(root_display_list.flatten(ContentStackingLevel));
// FIXME(pcwalton): This is really ugly and can't handle overflow: scroll. Refactor

View file

@ -79,8 +79,7 @@ impl Default for Au {
impl fmt::Show for Au {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let Au(n) = *self;
write!(f, "Au(au={} px={})", n, to_frac_px(*self))
write!(f, "{}px", to_frac_px(*self))
}}
impl Add<Au,Au> for Au {

@ -1 +1 @@
Subproject commit b41f144a3a8b6388d0956f341bcffa5bbaecc899
Subproject commit c733f78e06bd02f7498e93b391e0f6094d91786a

View file

@ -114,6 +114,7 @@ struct Reftest {
servo_args: Vec<String>,
render_mode: RenderMode,
is_flaky: bool,
experimental: bool,
}
struct TestLine<'a> {
@ -166,12 +167,14 @@ fn parse_lists(file: &str, servo_args: &[String], render_mode: RenderMode, id_of
let mut conditions_list = test_line.conditions.split(',');
let mut flakiness = RenderMode::empty();
let mut experimental = false;
for condition in conditions_list {
match condition {
"flaky_cpu" => flakiness.insert(CpuRendering),
"flaky_gpu" => flakiness.insert(GpuRendering),
"flaky_linux" => flakiness.insert(LinuxTarget),
"flaky_macos" => flakiness.insert(MacOsTarget),
"experimental" => experimental = true,
_ => (),
}
}
@ -184,6 +187,7 @@ fn parse_lists(file: &str, servo_args: &[String], render_mode: RenderMode, id_of
render_mode: render_mode,
servo_args: servo_args.iter().map(|x| x.clone()).collect(),
is_flaky: render_mode.intersects(flakiness),
experimental: experimental,
};
tests.push(make_test(reftest));
@ -212,7 +216,11 @@ fn capture(reftest: &Reftest, side: uint) -> png::Image {
if reftest.render_mode.contains(CpuRendering) {
args.push("-c".to_string());
}
args.push_all_move(vec!("-f".to_string(), "-o".to_string(), filename.clone(), reftest.files[side].clone()));
if reftest.experimental {
args.push("--experimental".to_string());
}
args.push_all(["-f".to_string(), "-o".to_string(), filename.clone(),
reftest.files[side].clone()]);
let retval = match Command::new("./servo").args(args.as_slice()).status() {
Ok(status) => status,

View file

@ -0,0 +1,415 @@
// 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.
extern crate png;
extern crate std;
extern crate test;
extern crate regex;
use std::ascii::StrAsciiExt;
use std::io;
use std::io::{File, Reader, Command};
use std::io::process::ExitStatus;
use std::os;
use test::{AutoColor, DynTestName, DynTestFn, TestDesc, TestOpts, TestDescAndFn};
use test::run_tests_console;
use regex::Regex;
bitflags!(
flags RenderMode: u32 {
static CpuRendering = 0x00000001,
static GpuRendering = 0x00000010,
static LinuxTarget = 0x00000100,
static MacOsTarget = 0x00001000,
static AndroidTarget = 0x00010000
}
)
fn main() {
let args = os::args();
let mut parts = args.tail().split(|e| "--" == e.as_slice());
let harness_args = parts.next().unwrap(); // .split() is never empty
let servo_args = parts.next().unwrap_or(&[]);
let (render_mode_string, base_path, testname) = match harness_args {
[] | [_] => fail!("USAGE: cpu|gpu base_path [testname regex]"),
[ref render_mode_string, ref base_path] => (render_mode_string, base_path, None),
[ref render_mode_string, ref base_path, ref testname, ..] => (render_mode_string, base_path, Some(Regex::new(testname.as_slice()).unwrap())),
};
let mut render_mode = match render_mode_string.as_slice() {
"cpu" => CpuRendering,
"gpu" => GpuRendering,
_ => fail!("First argument must specify cpu or gpu as rendering mode")
};
if cfg!(target_os = "linux") {
render_mode.insert(LinuxTarget);
}
if cfg!(target_os = "macos") {
render_mode.insert(MacOsTarget);
}
if cfg!(target_os = "android") {
render_mode.insert(AndroidTarget);
}
let mut all_tests = vec!();
println!("Scanning {} for manifests\n", base_path);
for file in io::fs::walk_dir(&Path::new(base_path.as_slice())).unwrap() {
let maybe_extension = file.extension_str();
match maybe_extension {
Some(extension) => {
if extension.to_ascii_lower().as_slice() == "list" && file.is_file() {
let manifest = file.as_str().unwrap();
let tests = parse_lists(manifest, servo_args, render_mode);
println!("\t{} [{} tests]", manifest, tests.len());
all_tests.push_all_move(tests);
}
}
_ => {}
}
}
let test_opts = TestOpts {
filter: testname,
run_ignored: false,
logfile: None,
run_tests: true,
run_benchmarks: false,
ratchet_noise_percent: None,
ratchet_metrics: None,
save_metrics: None,
test_shard: None,
nocapture: false,
color: AutoColor
};
match run_tests_console(&test_opts, all_tests) {
Ok(false) => os::set_exit_status(1), // tests failed
Err(_) => os::set_exit_status(2), // I/O-related failure
_ => (),
}
}
#[deriving(PartialEq)]
enum ReftestKind {
Same,
Different,
}
struct Reftest {
name: String,
kind: ReftestKind,
files: [String, ..2],
id: uint,
servo_args: Vec<String>,
render_mode: RenderMode,
<<<<<<< HEAD
is_flaky: bool,
||||||| merged common ancestors
flakiness: uint,
=======
flakiness: uint,
experimental: bool,
>>>>>>> Reftests can opt into --experimental
}
struct TestLine<'a> {
conditions: &'a str,
kind: &'a str,
file_left: &'a str,
file_right: &'a str,
}
fn parse_lists(file: &str, servo_args: &[String], render_mode: RenderMode) -> Vec<TestDescAndFn> {
let mut tests = Vec::new();
let mut next_id = 0;
let file_path = Path::new(file);
let contents = File::open_mode(&file_path, io::Open, io::Read)
.and_then(|mut f| f.read_to_string())
.ok().expect("Could not read file");
for line in contents.as_slice().lines() {
<<<<<<< HEAD
// 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],
||||||| merged common ancestors
// 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],
}
},
_ => {
fail!("reftest line: '{:s}' doesn't match '[CONDITIONS] KIND LEFT RIGHT'", line);
}
};
let kind = match test_line.kind {
"==" => Same,
"!=" => Different,
part => fail!("reftest line: '{:s}' has invalid kind '{:s}'", line, part)
};
let src_path = file_path.dir_path();
let src_dir = src_path.display().to_string();
let file_left = src_dir.clone().append("/").append(test_line.file_left);
let file_right = src_dir.append("/").append(test_line.file_right);
let mut conditions_list = test_line.conditions.split(',');
let mut flakiness = 0;
for condition in conditions_list {
match condition {
"flaky_cpu" => {
flakiness |= CpuRendering as uint;
=======
// 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],
}
},
_ => {
fail!("reftest line: '{:s}' doesn't match '[CONDITIONS] KIND LEFT RIGHT'", line);
}
};
let kind = match test_line.kind {
"==" => Same,
"!=" => Different,
part => fail!("reftest line: '{:s}' has invalid kind '{:s}'", line, part)
};
let src_path = file_path.dir_path();
let src_dir = src_path.display().to_string();
let file_left = src_dir.clone().append("/").append(test_line.file_left);
let file_right = src_dir.append("/").append(test_line.file_right);
let mut conditions_list = test_line.conditions.split(',');
let mut flakiness = 0;
let mut experimental = false;
for condition in conditions_list {
match condition {
"flaky_cpu" => {
flakiness |= CpuRendering as uint;
>>>>>>> Reftests can opt into --experimental
},
4 => TestLine {
conditions: parts[0],
kind: parts[1],
file_left: parts[2],
file_right: parts[3],
},
<<<<<<< HEAD
_ => fail!("reftest line: '{:s}' doesn't match '[CONDITIONS] KIND LEFT RIGHT'", line),
};
let kind = match test_line.kind {
"==" => Same,
"!=" => Different,
part => fail!("reftest line: '{:s}' has invalid kind '{:s}'", line, part)
};
let src_path = file_path.dir_path();
let src_dir = src_path.display().to_string();
let file_left = src_dir.clone().append("/").append(test_line.file_left);
let file_right = src_dir.append("/").append(test_line.file_right);
let mut conditions_list = test_line.conditions.split(',');
let mut flakiness = RenderMode::empty();
for condition in conditions_list {
match condition {
"flaky_cpu" => flakiness.insert(CpuRendering),
"flaky_gpu" => flakiness.insert(GpuRendering),
"flaky_linux" => flakiness.insert(LinuxTarget),
"flaky_macos" => flakiness.insert(MacOsTarget),
_ => (),
}
}
let reftest = Reftest {
name: format!("{} {} {}", test_line.file_left, test_line.kind, test_line.file_right),
kind: kind,
files: [file_left, file_right],
id: next_id,
render_mode: render_mode,
servo_args: servo_args.iter().map(|x| x.clone()).collect(),
is_flaky: render_mode.intersects(flakiness),
};
next_id += 1;
tests.push(make_test(reftest));
||||||| merged common ancestors
_ => {}
}
}
let reftest = Reftest {
name: test_line.file_left.to_string().append(" / ").append(test_line.file_right),
kind: kind,
files: [file_left, file_right],
id: next_id,
render_mode: render_mode,
servo_args: servo_args.iter().map(|x| x.clone()).collect(),
flakiness: flakiness,
};
next_id += 1;
tests.push(make_test(reftest));
=======
"experimental" => {
experimental = true;
},
_ => {}
}
}
let reftest = Reftest {
name: test_line.file_left.to_string().append(" / ").append(test_line.file_right),
kind: kind,
files: [file_left, file_right],
id: next_id,
render_mode: render_mode,
servo_args: servo_args.iter().map(|x| x.clone()).collect(),
flakiness: flakiness,
experimental: experimental,
};
next_id += 1;
tests.push(make_test(reftest));
>>>>>>> Reftests can opt into --experimental
}
tests
}
fn make_test(reftest: Reftest) -> TestDescAndFn {
let name = reftest.name.clone();
TestDescAndFn {
desc: TestDesc {
name: DynTestName(name),
ignore: false,
should_fail: false,
},
testfn: DynTestFn(proc() {
check_reftest(reftest);
}),
}
}
fn capture(reftest: &Reftest, side: uint) -> png::Image {
let filename = format!("/tmp/servo-reftest-{:06u}-{:u}.png", reftest.id, side);
let mut args = reftest.servo_args.clone();
// GPU rendering is the default
if reftest.render_mode.contains(CpuRendering) {
args.push("-c".to_string());
}
if reftest.experimental {
args.push("--experimental".to_string());
}
args.push_all(["-f".to_string(), "-o".to_string(), filename.clone(),
reftest.files[side].clone()]);
let retval = match Command::new("./servo").args(args.as_slice()).status() {
Ok(status) => status,
Err(e) => fail!("failed to execute process: {}", e),
};
assert!(retval == ExitStatus(0));
png::load_png(&from_str::<Path>(filename.as_slice()).unwrap()).unwrap()
}
fn check_reftest(reftest: Reftest) {
let left = capture(&reftest, 0);
let right = capture(&reftest, 1);
let pixels = left.pixels.iter().zip(right.pixels.iter()).map(|(&a, &b)| {
if a as i8 - b as i8 == 0 {
// 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_str = format!("/tmp/servo-reftest-{:06u}-diff.png", reftest.id);
let output = from_str::<Path>(output_str.as_slice()).unwrap();
let mut img = png::Image {
width: left.width,
height: left.height,
color_type: png::RGBA8,
pixels: pixels,
};
let res = png::store_png(&mut img, &output);
assert!(res.is_ok());
match (reftest.kind, reftest.is_flaky) {
(Same, true) => println!("flaky test - rendering difference: {}", output_str),
(Same, false) => fail!("rendering difference: {}", output_str),
(Different, _) => {} // Result was different and that's what was expected
}
} else {
assert!(reftest.is_flaky || reftest.kind == Same);
}
}

View file

@ -86,6 +86,7 @@ flaky_cpu == linebreak_simple_a.html linebreak_simple_b.html
== position_fixed_overflow_a.html position_fixed_overflow_b.html
== noscript.html noscript_ref.html
== pseudo_inherit.html pseudo_inherit_ref.html
experimental == vertical-lr-blocks.html vertical-lr-blocks_ref.html
== float_intrinsic_height.html float_intrinsic_height_ref.html
== table_auto_width.html table_auto_width_ref.html
== inline_whitespace_b.html inline_whitespace_ref.html

View file

@ -0,0 +1,18 @@
<!doctype html>
<html>
<head>
<style>
html { writing-mode: vertical-rl }
body { margin: 10px }
div { border: blue solid 5px; line-height: 30px; height: 500px }
p { background: green; margin: 40px 20px }
p + p { margin-top: 60px }
</style>
</head>
<body>
<div>
<p>&nbsp;</p>
<p>&nbsp;</p>
</div>
</body>
</html>

View file

@ -0,0 +1,18 @@
<!doctype html>
<html>
<head>
<style>
div { border: blue solid 5px; position: absolute;
top: 10px; right: 10px; bottom: 10px; width: 120px; height: 500px }
p { background: green; margin: 0; position: absolute;
top: 40px; right: 20px; bottom: 40px; width: 30px }
p + p { right: 70px; top: 60px }
</style>
</head>
<body>
<div>
<p>&nbsp;</p>
<p>&nbsp;</p>
</div>
</body>
</html>