servo/components/layout_2020/tests/floats.rs
Simon Wülker bb0d08432e
Migrate to the 2024 edition (#35755)
* Migrate to 2024 edition

Signed-off-by: Simon Wülker <simon.wuelker@arcor.de>

* Allow unsafe_op_in_unsafe_fn lint

This lint warns by default in the 2024
edition, but is *way* too noisy for servo.

We might enable it in the future, but not now.

Signed-off-by: Simon Wülker <simon.wuelker@arcor.de>

* Compile using the 2024 edition

Signed-off-by: Simon Wülker <simon.wuelker@arcor.de>

---------

Signed-off-by: Simon Wülker <simon.wuelker@arcor.de>
2025-03-13 10:28:11 +00:00

854 lines
30 KiB
Rust

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
//! Property-based randomized testing for the core float layout algorithm.
use std::f32::INFINITY;
use std::ops::Range;
use std::panic::{self, PanicHookInfo};
use std::sync::{Mutex, MutexGuard};
use std::{thread, u32};
use app_units::Au;
use euclid::num::Zero;
use layout_2020::flow::float::{
Clear, ContainingBlockPositionInfo, FloatBand, FloatBandNode, FloatBandTree, FloatContext,
FloatSide, PlacementInfo,
};
use layout_2020::geom::{LogicalRect, LogicalVec2};
use quickcheck::{Arbitrary, Gen};
static PANIC_HOOK_MUTEX: Mutex<()> = Mutex::new(());
// Suppresses panic messages. Some tests need to fail and we don't want them to spam the console.
// Note that, because the panic hook is process-wide, tests that are expected to fail might
// suppress panic messages from other failing tests. To work around this, run failing tests one at
// a time or use only a single test thread.
struct PanicMsgSuppressor<'a> {
#[allow(dead_code)]
mutex_guard: MutexGuard<'a, ()>,
prev_hook: Option<Box<dyn Fn(&PanicHookInfo<'_>) + 'static + Sync + Send>>,
}
impl<'a> PanicMsgSuppressor<'a> {
fn new(mutex_guard: MutexGuard<'a, ()>) -> PanicMsgSuppressor<'a> {
let prev_hook = panic::take_hook();
panic::set_hook(Box::new(|_| ()));
PanicMsgSuppressor {
mutex_guard,
prev_hook: Some(prev_hook),
}
}
}
impl<'a> Drop for PanicMsgSuppressor<'a> {
fn drop(&mut self) {
panic::set_hook(self.prev_hook.take().unwrap())
}
}
// AA tree helpers
#[derive(Clone, Debug)]
struct FloatBandWrapper(FloatBand);
impl Arbitrary for FloatBandWrapper {
fn arbitrary(generator: &mut Gen) -> FloatBandWrapper {
let top: u32 = u32::arbitrary(generator);
let inline_start: Option<u32> = Some(u32::arbitrary(generator));
let inline_end: Option<u32> = Some(u32::arbitrary(generator));
FloatBandWrapper(FloatBand {
top: Au::from_f32_px(top as f32),
inline_start: inline_start.map(|value| Au::from_f32_px(value as f32)),
inline_end: inline_end.map(|value| Au::from_f32_px(value as f32)),
})
}
}
#[derive(Clone, Debug)]
struct FloatRangeInput {
start_index: u32,
side: FloatSide,
length: u32,
}
impl Arbitrary for FloatRangeInput {
fn arbitrary(generator: &mut Gen) -> FloatRangeInput {
let start_index: u32 = Arbitrary::arbitrary(generator);
let is_left: bool = Arbitrary::arbitrary(generator);
let length: u32 = Arbitrary::arbitrary(generator);
FloatRangeInput {
start_index,
side: if is_left {
FloatSide::InlineStart
} else {
FloatSide::InlineEnd
},
length,
}
}
}
// AA tree predicates
fn check_node_ordering(node: &FloatBandNode) {
let mid = node.band.top;
if let Some(ref left) = node.left.0 {
assert!(left.band.top < mid);
}
if let Some(ref right) = node.right.0 {
assert!(right.band.top > mid);
}
if let Some(ref left) = node.left.0 {
check_node_ordering(left);
}
if let Some(ref right) = node.right.0 {
check_node_ordering(right);
}
}
// https://en.wikipedia.org/wiki/AA_tree#Balancing_rotations
fn check_node_balance(node: &FloatBandNode) {
// 1. The level of every leaf node is one.
if node.left.0.is_none() && node.right.0.is_none() {
assert_eq!(node.level, 1);
}
// 2. The level of every left child is exactly one less than that of its parent.
if let Some(ref left) = node.left.0 {
assert_eq!(left.level, node.level - 1);
}
// 3. The level of every right child is equal to or one less than that of its parent.
if let Some(ref right) = node.right.0 {
assert!(right.level == node.level || right.level == node.level - 1);
}
// 4. The level of every right grandchild is strictly less than that of its grandparent.
if let Some(ref right) = node.right.0 {
if let Some(ref right_right) = right.right.0 {
assert!(right_right.level < node.level);
}
}
// 5. Every node of level greater than one has two children.
if node.level > 1 {
assert!(node.left.0.is_some() && node.right.0.is_some());
}
}
fn check_tree_ordering(tree: FloatBandTree) {
if let Some(ref root) = tree.root.0 {
check_node_ordering(root);
}
}
fn check_tree_balance(tree: FloatBandTree) {
if let Some(ref root) = tree.root.0 {
check_node_balance(root);
}
}
fn check_tree_find(tree: &FloatBandTree, block_position: Au, sorted_bands: &[FloatBand]) {
let found_band = tree
.find(block_position)
.expect("Couldn't find the band in the tree!");
let reference_band_index = sorted_bands
.iter()
.position(|band| band.top > block_position)
.expect("Couldn't find the reference band!") -
1;
let reference_band = &sorted_bands[reference_band_index];
assert_eq!(found_band.top, reference_band.top);
assert_eq!(found_band.inline_start, reference_band.inline_start);
assert_eq!(found_band.inline_end, reference_band.inline_end);
}
fn check_tree_find_next(tree: &FloatBandTree, block_position: Au, sorted_bands: &[FloatBand]) {
let found_band = tree
.find_next(block_position)
.expect("Couldn't find the band in the tree!");
let reference_band_index = sorted_bands
.iter()
.position(|band| band.top > block_position)
.expect("Couldn't find the reference band!");
let reference_band = &sorted_bands[reference_band_index];
assert_eq!(found_band.top, reference_band.top);
assert_eq!(found_band.inline_start, reference_band.inline_start);
assert_eq!(found_band.inline_end, reference_band.inline_end);
}
fn check_node_range_setting(
node: &FloatBandNode,
block_range: &Range<Au>,
side: FloatSide,
value: Au,
) {
if node.band.top >= block_range.start && node.band.top < block_range.end {
match side {
FloatSide::InlineStart => assert!(node.band.inline_start.unwrap() >= value),
FloatSide::InlineEnd => assert!(node.band.inline_end.unwrap() <= value),
}
}
if let Some(ref left) = node.left.0 {
check_node_range_setting(left, block_range, side, value)
}
if let Some(ref right) = node.right.0 {
check_node_range_setting(right, block_range, side, value)
}
}
fn check_tree_range_setting(
tree: &FloatBandTree,
block_range: &Range<Au>,
side: FloatSide,
value: Au,
) {
if let Some(ref root) = tree.root.0 {
check_node_range_setting(root, block_range, side, value)
}
}
// AA tree unit tests
// Tests that the tree is a properly-ordered binary tree.
#[test]
fn test_tree_ordering() {
let f: fn(Vec<FloatBandWrapper>) = check;
quickcheck::quickcheck(f);
fn check(bands: Vec<FloatBandWrapper>) {
let mut tree = FloatBandTree::new();
for FloatBandWrapper(band) in bands {
tree = tree.insert(band);
}
check_tree_ordering(tree);
}
}
// Tests that the tree is balanced (i.e. AA tree invariants are maintained).
#[test]
fn test_tree_balance() {
let f: fn(Vec<FloatBandWrapper>) = check;
quickcheck::quickcheck(f);
fn check(bands: Vec<FloatBandWrapper>) {
let mut tree = FloatBandTree::new();
for FloatBandWrapper(band) in bands {
tree = tree.insert(band);
}
check_tree_balance(tree);
}
}
// Tests that the `find()` method works.
#[test]
fn test_tree_find() {
let f: fn(Vec<FloatBandWrapper>, Vec<u16>) = check;
quickcheck::quickcheck(f);
fn check(bands: Vec<FloatBandWrapper>, lookups: Vec<u16>) {
let mut bands: Vec<FloatBand> = bands.into_iter().map(|band| band.0).collect();
bands.push(FloatBand {
top: Au::zero(),
inline_start: None,
inline_end: None,
});
bands.push(FloatBand {
top: Au::from_f32_px(INFINITY),
inline_start: None,
inline_end: None,
});
let mut tree = FloatBandTree::new();
for band in &bands {
tree = tree.insert(*band);
}
bands.sort_by(|a, b| a.top.partial_cmp(&b.top).unwrap());
for lookup in lookups {
check_tree_find(&tree, Au::from_f32_px(lookup as f32), &bands);
}
}
}
// Tests that the `find_next()` method works.
#[test]
fn test_tree_find_next() {
let f: fn(Vec<FloatBandWrapper>, Vec<u16>) = check;
quickcheck::quickcheck(f);
fn check(bands: Vec<FloatBandWrapper>, lookups: Vec<u16>) {
let mut bands: Vec<FloatBand> = bands.into_iter().map(|band| band.0).collect();
bands.push(FloatBand {
top: Au::zero(),
inline_start: None,
inline_end: None,
});
bands.push(FloatBand {
top: Au::from_f32_px(INFINITY),
inline_start: None,
inline_end: None,
});
bands.sort_by(|a, b| a.top.partial_cmp(&b.top).unwrap());
bands.dedup_by(|a, b| a.top == b.top);
let mut tree = FloatBandTree::new();
for band in &bands {
tree = tree.insert(*band);
}
for lookup in lookups {
check_tree_find_next(&tree, Au::from_f32_px(lookup as f32), &bands);
}
}
}
// Tests that `set_range()` works.
#[test]
fn test_tree_range_setting() {
let f: fn(Vec<FloatBandWrapper>, Vec<FloatRangeInput>) = check;
quickcheck::quickcheck(f);
fn check(bands: Vec<FloatBandWrapper>, ranges: Vec<FloatRangeInput>) {
let mut tree = FloatBandTree::new();
for FloatBandWrapper(band) in &bands {
tree = tree.insert(*band);
}
let mut tops: Vec<Au> = bands.iter().map(|band| band.0.top).collect();
tops.push(Au::from_f32_px(INFINITY));
tops.sort_by(|a, b| a.to_px().partial_cmp(&b.to_px()).unwrap());
for range in ranges {
let start = range.start_index.min(tops.len() as u32 - 1);
let end = (range.start_index as u64 + range.length as u64).min(tops.len() as u64 - 1);
let block_range = tops[start as usize]..tops[end as usize];
let length = Au::from_px(range.length as i32);
let new_tree = tree.set_range(&block_range, range.side, length);
check_tree_range_setting(&new_tree, &block_range, range.side, length);
}
}
}
// Float predicates
#[derive(Clone, Debug)]
struct FloatInput {
// Information needed to place the float.
info: PlacementInfo,
// The float may be placed no higher than this line. This simulates the effect of line boxes
// per CSS 2.1 § 9.5.1 rule 6.
ceiling: Au,
/// Containing block positioning information, which is used to track the current offsets
/// from the float containing block formatting context to the current containing block.
containing_block_info: ContainingBlockPositionInfo,
}
impl Arbitrary for FloatInput {
fn arbitrary(generator: &mut Gen) -> FloatInput {
// See #29819: Limit the maximum size of all f32 values here because
// massive float values will start to introduce very bad floating point
// errors.
// TODO: This should be be addressed in a better way. Perhaps we should
// reintroduce the use of app_units in Layout 2020.
let width = u32::arbitrary(generator) % 12345;
let height = u32::arbitrary(generator) % 12345;
let is_left = bool::arbitrary(generator);
let ceiling = u32::arbitrary(generator) % 12345;
let left = u32::arbitrary(generator) % 12345;
let containing_block_width = u32::arbitrary(generator) % 12345;
let clear = u8::arbitrary(generator);
FloatInput {
info: PlacementInfo {
size: LogicalVec2 {
inline: Au::from_f32_px(width as f32),
block: Au::from_f32_px(height as f32),
},
side: if is_left {
FloatSide::InlineStart
} else {
FloatSide::InlineEnd
},
clear: new_clear(clear),
},
ceiling: Au::from_f32_px(ceiling as f32),
containing_block_info: ContainingBlockPositionInfo::new_with_inline_offsets(
Au::from_f32_px(left as f32),
Au::from_f32_px(left as f32 + containing_block_width as f32),
),
}
}
fn shrink(&self) -> Box<dyn Iterator<Item = FloatInput>> {
let mut this = (*self).clone();
let mut shrunk = false;
if let Some(inline_size) = self.info.size.inline.to_px().shrink().next() {
this.info.size.inline = Au::from_px(inline_size);
shrunk = true;
}
if let Some(block_size) = self.info.size.block.to_px().shrink().next() {
this.info.size.block = Au::from_px(block_size);
shrunk = true;
}
if let Some(clear) = (self.info.clear as u8).shrink().next() {
this.info.clear = new_clear(clear);
shrunk = true;
}
if let Some(left) = self
.containing_block_info
.inline_start
.to_px()
.shrink()
.next()
{
this.containing_block_info.inline_start = Au::from_px(left);
shrunk = true;
}
if let Some(right) = self
.containing_block_info
.inline_end
.to_px()
.shrink()
.next()
{
this.containing_block_info.inline_end = Au::from_px(right);
shrunk = true;
}
if let Some(ceiling) = self.ceiling.to_px().shrink().next() {
this.ceiling = Au::from_px(ceiling);
shrunk = true;
}
if shrunk {
quickcheck::single_shrinker(this)
} else {
quickcheck::empty_shrinker()
}
}
}
fn new_clear(value: u8) -> Clear {
match value & 3 {
0 => Clear::None,
1 => Clear::InlineStart,
2 => Clear::InlineEnd,
_ => Clear::Both,
}
}
#[derive(Clone)]
struct FloatPlacement {
float_context: FloatContext,
placed_floats: Vec<PlacedFloat>,
}
// Information about the placement of a float.
#[derive(Clone)]
struct PlacedFloat {
origin: LogicalVec2<Au>,
info: PlacementInfo,
ceiling: Au,
containing_block_info: ContainingBlockPositionInfo,
}
impl Drop for FloatPlacement {
fn drop(&mut self) {
if !thread::panicking() {
return;
}
// Dump the float context for debugging.
eprintln!("Failing float placement:");
for placed_float in &self.placed_floats {
eprintln!(
" * {:?} @ {:?}, T {:?} L {:?} R {:?}",
placed_float.info,
placed_float.origin,
placed_float.ceiling,
placed_float.containing_block_info.inline_start,
placed_float.containing_block_info.inline_end,
);
}
eprintln!("Bands:\n{:?}\n", self.float_context.bands);
}
}
impl PlacedFloat {
fn rect(&self) -> LogicalRect<Au> {
LogicalRect {
start_corner: self.origin,
size: self.info.size,
}
}
}
impl FloatPlacement {
fn place(floats: Vec<FloatInput>) -> FloatPlacement {
let mut float_context = FloatContext::new(Au::from_f32_px(INFINITY));
let mut placed_floats = vec![];
for float in floats {
let ceiling = float.ceiling;
float_context.set_ceiling_from_non_floats(ceiling);
float_context.containing_block_info = float.containing_block_info;
placed_floats.push(PlacedFloat {
origin: float_context.add_float(&float.info),
info: float.info,
ceiling,
containing_block_info: float.containing_block_info,
})
}
FloatPlacement {
float_context,
placed_floats,
}
}
}
// From CSS 2.1 § 9.5.1 [1].
//
// [1]: https://www.w3.org/TR/CSS2/visuren.html#float-position
// 1. The left outer edge of a left-floating box may not be to the left of the left edge of its
// containing block. An analogous rule holds for right-floating elements.
fn check_floats_rule_1(placement: &FloatPlacement) {
for placed_float in &placement.placed_floats {
match placed_float.info.side {
FloatSide::InlineStart => assert!(
placed_float.origin.inline >= placed_float.containing_block_info.inline_start
),
FloatSide::InlineEnd => {
assert!(
placed_float.rect().max_inline_position() <=
placed_float.containing_block_info.inline_end
)
},
}
}
}
// 2. If the current box is left-floating, and there are any left-floating boxes generated by
// elements earlier in the source document, then for each such earlier box, either the left
// outer edge of the current box must be to the right of the right outer edge of the earlier
// box, or its top must be lower than the bottom of the earlier box. Analogous rules hold for
// right-floating boxes.
fn check_floats_rule_2(placement: &FloatPlacement) {
for (this_float_index, this_float) in placement.placed_floats.iter().enumerate() {
for prev_float in &placement.placed_floats[0..this_float_index] {
match (this_float.info.side, prev_float.info.side) {
(FloatSide::InlineStart, FloatSide::InlineStart) => {
assert!(
this_float.origin.inline >= prev_float.rect().max_inline_position() ||
this_float.origin.block >= prev_float.rect().max_block_position()
);
},
(FloatSide::InlineEnd, FloatSide::InlineEnd) => {
assert!(
this_float.rect().max_inline_position() <= prev_float.origin.inline ||
this_float.origin.block >= prev_float.rect().max_block_position()
);
},
(FloatSide::InlineStart, FloatSide::InlineEnd) |
(FloatSide::InlineEnd, FloatSide::InlineStart) => {},
}
}
}
}
// 3. The right outer edge of a left-floating box may not be to the right of the left outer edge of
// any right-floating box that is next to it. Analogous rules hold for right-floating elements.
fn check_floats_rule_3(placement: &FloatPlacement) {
for (this_float_index, this_float) in placement.placed_floats.iter().enumerate() {
for other_float in &placement.placed_floats[0..this_float_index] {
// This logic to check intersection is complicated by the fact that we need to treat
// zero-height floats later in the document as "next to" floats earlier in the
// document. Otherwise we might end up with a situation like:
//
// <div id="a" style="float: left; width: 32px; height: 32px"></div>
// <div id="b" style="float: right; width: 0px; height: 0px"></div>
//
// Where the top of `b` should probably be 32px per Rule 3, but unless this distinction
// is made the top of `b` could legally be 0px.
if this_float.origin.block >= other_float.rect().max_block_position() ||
(this_float.info.size.block == Au::zero() &&
this_float.rect().max_block_position() < other_float.origin.block) ||
(this_float.info.size.block > Au::zero() &&
this_float.rect().max_block_position() <= other_float.origin.block)
{
continue;
}
match (this_float.info.side, other_float.info.side) {
(FloatSide::InlineStart, FloatSide::InlineEnd) => {
assert!(this_float.rect().max_inline_position() <= other_float.origin.inline);
},
(FloatSide::InlineEnd, FloatSide::InlineStart) => {
assert!(this_float.origin.inline >= other_float.rect().max_inline_position());
},
(FloatSide::InlineStart, FloatSide::InlineStart) |
(FloatSide::InlineEnd, FloatSide::InlineEnd) => {},
}
}
}
}
// 4. A floating box's outer top may not be higher than the top of its containing block. When the
// float occurs between two collapsing margins, the float is positioned as if it had an
// otherwise empty anonymous block parent taking part in the flow. The position of such a parent
// is defined by the rules in the section on margin collapsing.
fn check_floats_rule_4(placement: &FloatPlacement) {
for placed_float in &placement.placed_floats {
assert!(placed_float.origin.block >= Au::zero());
}
}
// 5. The outer top of a floating box may not be higher than the outer top of any block or floated
// box generated by an element earlier in the source document.
fn check_floats_rule_5(placement: &FloatPlacement) {
let mut block_position = Au::zero();
for placed_float in &placement.placed_floats {
assert!(placed_float.origin.block >= block_position);
block_position = placed_float.origin.block;
}
}
// 6. The outer top of an element's floating box may not be higher than the top of any line-box
// containing a box generated by an element earlier in the source document.
fn check_floats_rule_6(placement: &FloatPlacement) {
for placed_float in &placement.placed_floats {
assert!(placed_float.origin.block >= placed_float.ceiling);
}
}
// 7. A left-floating box that has another left-floating box to its left may not have its right
// outer edge to the right of its containing block's right edge. (Loosely: a left float may not
// stick out at the right edge, unless it is already as far to the left as possible.) An
// analogous rule holds for right-floating elements.
fn check_floats_rule_7(placement: &FloatPlacement) {
for (placed_float_index, placed_float) in placement.placed_floats.iter().enumerate() {
// Only consider floats that stick out.
match placed_float.info.side {
FloatSide::InlineStart => {
if placed_float.rect().max_inline_position() <=
placed_float.containing_block_info.inline_end
{
continue;
}
},
FloatSide::InlineEnd => {
if placed_float.origin.inline >= placed_float.containing_block_info.inline_start {
continue;
}
},
}
// Make sure there are no previous floats to the left or right.
for prev_float in &placement.placed_floats[0..placed_float_index] {
assert!(
prev_float.info.side != placed_float.info.side ||
prev_float.rect().max_block_position() <= placed_float.origin.block ||
prev_float.origin.block >= placed_float.rect().max_block_position()
);
}
}
}
// 8. A floating box must be placed as high as possible.
fn check_floats_rule_8(floats_and_perturbations: Vec<(FloatInput, u32)>) {
let floats = floats_and_perturbations
.iter()
.map(|(float, _)| (*float).clone())
.collect();
let placement = FloatPlacement::place(floats);
for (float_index, &(_, perturbation)) in floats_and_perturbations.iter().enumerate() {
if perturbation == 0 {
continue;
}
let mut placement = placement.clone();
placement.placed_floats[float_index].origin.block -= Au::from_f32_px(perturbation as f32);
let result = {
let mutex_guard = PANIC_HOOK_MUTEX.lock().unwrap();
let _suppressor = PanicMsgSuppressor::new(mutex_guard);
panic::catch_unwind(|| check_basic_float_rules(&placement))
};
assert!(result.is_err());
}
}
// 9. A left-floating box must be put as far to the left as possible, a right-floating box as far
// to the right as possible. A higher position is preferred over one that is further to the
// left/right.
fn check_floats_rule_9(floats_and_perturbations: Vec<(FloatInput, u32)>) {
let floats = floats_and_perturbations
.iter()
.map(|(float, _)| (*float).clone())
.collect();
let placement = FloatPlacement::place(floats);
for (float_index, &(_, perturbation)) in floats_and_perturbations.iter().enumerate() {
if perturbation == 0 {
continue;
}
let mut placement = placement.clone();
{
let placed_float = &mut placement.placed_floats[float_index];
let perturbation = Au::from_f32_px(perturbation as f32);
match placed_float.info.side {
FloatSide::InlineStart => placed_float.origin.inline -= perturbation,
FloatSide::InlineEnd => placed_float.origin.inline += perturbation,
}
}
let result = {
let mutex_guard = PANIC_HOOK_MUTEX.lock().unwrap();
let _suppressor = PanicMsgSuppressor::new(mutex_guard);
panic::catch_unwind(|| check_basic_float_rules(&placement))
};
assert!(result.is_err());
}
}
// From CSS 2.1 § 9.5.2 (https://www.w3.org/TR/CSS2/visuren.html#propdef-clear):
//
// 10. The top outer edge of the float must be below the bottom outer edge of all earlier
// left-floating boxes (in the case of 'clear: left'), or all earlier right-floating boxes (in
// the case of 'clear: right'), or both ('clear: both').
fn check_floats_rule_10(placement: &FloatPlacement) {
let mut block_position = Au::zero();
for placed_float in &placement.placed_floats {
assert!(placed_float.origin.block >= block_position);
block_position = placed_float.origin.block;
}
for (this_float_index, this_float) in placement.placed_floats.iter().enumerate() {
if this_float.info.clear == Clear::None {
continue;
}
for other_float in &placement.placed_floats[0..this_float_index] {
// This logic to check intersection is complicated by the fact that we need to treat
// zero-height floats later in the document as "next to" floats earlier in the
// document. Otherwise we might end up with a situation like:
//
// <div id="a" style="float: left; width: 32px; height: 32px"></div>
// <div id="b" style="float: right; width: 0px; height: 0px"></div>
//
// Where the top of `b` should probably be 32px per Rule 3, but unless this distinction
// is made the top of `b` could legally be 0px.
if this_float.origin.block >= other_float.rect().max_block_position() ||
(this_float.info.size.block == Au::zero() &&
this_float.rect().max_block_position() < other_float.origin.block) ||
(this_float.info.size.block > Au::zero() &&
this_float.rect().max_block_position() <= other_float.origin.block)
{
continue;
}
match this_float.info.clear {
Clear::InlineStart => assert_ne!(other_float.info.side, FloatSide::InlineStart),
Clear::InlineEnd => assert_ne!(other_float.info.side, FloatSide::InlineEnd),
Clear::Both => assert!(false),
Clear::None => unreachable!(),
}
}
}
}
// Checks that rule 1-7 and rule 10 hold (i.e. all rules that don't specify that floats are placed
// "as far as possible" in some direction).
fn check_basic_float_rules(placement: &FloatPlacement) {
check_floats_rule_1(placement);
check_floats_rule_2(placement);
check_floats_rule_3(placement);
check_floats_rule_4(placement);
check_floats_rule_5(placement);
check_floats_rule_6(placement);
check_floats_rule_7(placement);
check_floats_rule_10(placement);
}
// Float unit tests
#[test]
fn test_floats_rule_1() {
let f: fn(Vec<FloatInput>) = check;
quickcheck::quickcheck(f);
fn check(floats: Vec<FloatInput>) {
check_floats_rule_1(&FloatPlacement::place(floats));
}
}
#[test]
fn test_floats_rule_2() {
let f: fn(Vec<FloatInput>) = check;
quickcheck::quickcheck(f);
fn check(floats: Vec<FloatInput>) {
check_floats_rule_2(&FloatPlacement::place(floats));
}
}
#[test]
fn test_floats_rule_3() {
let f: fn(Vec<FloatInput>) = check;
quickcheck::quickcheck(f);
fn check(floats: Vec<FloatInput>) {
check_floats_rule_3(&FloatPlacement::place(floats));
}
}
#[test]
fn test_floats_rule_4() {
let f: fn(Vec<FloatInput>) = check;
quickcheck::quickcheck(f);
fn check(floats: Vec<FloatInput>) {
check_floats_rule_4(&FloatPlacement::place(floats));
}
}
#[test]
fn test_floats_rule_5() {
let f: fn(Vec<FloatInput>) = check;
quickcheck::quickcheck(f);
fn check(floats: Vec<FloatInput>) {
check_floats_rule_5(&FloatPlacement::place(floats));
}
}
#[test]
fn test_floats_rule_6() {
let f: fn(Vec<FloatInput>) = check;
quickcheck::quickcheck(f);
fn check(floats: Vec<FloatInput>) {
check_floats_rule_6(&FloatPlacement::place(floats));
}
}
#[test]
fn test_floats_rule_7() {
let f: fn(Vec<FloatInput>) = check;
quickcheck::quickcheck(f);
fn check(floats: Vec<FloatInput>) {
check_floats_rule_7(&FloatPlacement::place(floats));
}
}
#[test]
fn test_floats_rule_8() {
let f: fn(Vec<(FloatInput, u32)>) = check;
quickcheck::quickcheck(f);
fn check(floats: Vec<(FloatInput, u32)>) {
check_floats_rule_8(floats);
}
}
#[test]
fn test_floats_rule_9() {
let f: fn(Vec<(FloatInput, u32)>) = check;
quickcheck::quickcheck(f);
fn check(floats: Vec<(FloatInput, u32)>) {
check_floats_rule_9(floats);
}
}
#[test]
fn test_floats_rule_10() {
let f: fn(Vec<FloatInput>) = check;
quickcheck::quickcheck(f);
fn check(floats: Vec<FloatInput>) {
check_floats_rule_10(&FloatPlacement::place(floats));
}
}