use super::*;
use crate::scoring::independent_groups;
use drmkit_fmt::{
ARM_16X16_BLOCK_U_INTERLEAVED, BandwidthClass, FormatTable, Modifier, fourcc, mod_code, vendor,
};
fn placed_layer() -> Layer {
let mut layer = Layer::new();
layer
.set_property(PropTag::FbId, 42)
.set_property(PropTag::FbModifier, 0)
.set_property(PropTag::CrtcId, 1)
.set_property(PropTag::CrtcX, 0)
.set_property(PropTag::CrtcY, 0)
.set_property(PropTag::CrtcW, 1920)
.set_property(PropTag::CrtcH, 1080)
.set_property(PropTag::SrcX, 0)
.set_property(PropTag::SrcY, 0)
.set_property(PropTag::SrcW, 1920 << 16)
.set_property(PropTag::SrcH, 1080 << 16)
.set_property(PropTag::PixelFormat, u64::from(fourcc::XRGB8888));
layer
}
#[test]
fn every_prop_tag_is_classified() {
assert_eq!(
PropTag::ALL.len(),
NUM_PROPS,
"PropTag::ALL must list every variant"
);
let mut seen_indices: Vec<usize> = PropTag::ALL.iter().map(|t| t.index()).collect();
seen_indices.sort_unstable();
seen_indices.dedup();
assert_eq!(
seen_indices.len(),
NUM_PROPS,
"every tag must have a distinct index"
);
assert_eq!(
seen_indices,
(0..NUM_PROPS).collect::<Vec<_>>(),
"indices must be contiguous from zero -- the property array is indexed by them"
);
for tag in PropTag::ALL {
let _: PropClass = tag.class();
}
}
#[test]
fn only_fb_id_and_in_fence_fd_are_content() {
let content: Vec<PropTag> = PropTag::ALL
.into_iter()
.filter(|t| t.class() == PropClass::Content)
.collect();
assert_eq!(
content,
vec![PropTag::FbId, PropTag::InFenceFd],
"content is exactly FB_ID and IN_FENCE_FD; everything else is placement"
);
}
#[test]
fn property_hash_isolates_content_from_placement() {
let base = placed_layer();
let base_hash = base.property_hash();
for tag in PropTag::ALL
.into_iter()
.filter(|t| t.class() == PropClass::Content)
{
let mut layer = base.clone();
layer.set_property(tag, 0xdead_beef);
assert_eq!(
layer.property_hash(),
base_hash,
"{} is content and must not move the hash",
tag.name()
);
}
for tag in PropTag::ALL
.into_iter()
.filter(|t| t.class() == PropClass::Placement)
{
let mut layer = base.clone();
let previous = layer.property(tag).unwrap_or(0);
layer.set_property(tag, previous.wrapping_add(0x5a5a));
assert_ne!(
layer.property_hash(),
base_hash,
"{} is placement and must move the hash",
tag.name()
);
}
}
#[test]
fn newly_set_placement_property_moves_the_hash() {
let mut layer = placed_layer();
let before = layer.property_hash();
assert_eq!(layer.property(PropTag::Rotation), None);
layer.set_property(PropTag::Rotation, 1);
assert_ne!(layer.property_hash(), before);
}
#[test]
fn property_hash_is_order_independent() {
let mut forward = Layer::new();
for tag in PropTag::ALL {
forward.set_property(tag, tag.index() as u64 * 7 + 1);
}
let mut backward = Layer::new();
for tag in PropTag::ALL.into_iter().rev() {
backward.set_property(tag, tag.index() as u64 * 7 + 1);
}
assert_eq!(forward.property_hash(), backward.property_hash());
}
#[test]
fn property_hash_cache_is_invalidated_on_mutation() {
let mut layer = placed_layer();
let first = layer.property_hash();
assert_eq!(layer.property_hash(), first, "second read is memoized");
layer.set_property(PropTag::CrtcX, 64);
assert_ne!(layer.property_hash(), first, "mutation must invalidate");
layer.disable();
assert_ne!(layer.property_hash(), first, "disable must invalidate too");
}
#[test]
fn disable_sets_fb_id_to_zero_and_leaves_the_rest() {
let mut layer = placed_layer();
let before = layer.property_hash();
layer.disable();
assert_eq!(layer.property(PropTag::FbId), Some(0));
assert_eq!(
layer.property(PropTag::CrtcW),
Some(1920),
"geometry survives"
);
assert_eq!(
layer.property(PropTag::PixelFormat),
Some(u64::from(fourcc::XRGB8888)),
"format survives"
);
assert_eq!(
layer.property_hash(),
before,
"FB_ID is content, so disabling must not move the placement hash"
);
}
#[test]
fn setting_the_same_value_is_a_no_op() {
let mut layer = Layer::new();
layer.set_property(PropTag::FbId, 42);
layer.mark_clean();
assert!(!layer.is_dirty());
layer.set_property(PropTag::FbId, 42);
assert!(
!layer.is_dirty(),
"an unchanged value must not dirty the layer"
);
layer.set_property(PropTag::FbId, 43);
assert!(layer.is_dirty(), "a real change must");
}
#[test]
fn a_no_op_write_does_not_invalidate_the_hash_cache() {
let mut layer = placed_layer();
let before = layer.property_hash();
layer.set_property(PropTag::CrtcW, 1920); assert_eq!(layer.property_hash(), before);
layer.set_property(PropTag::CrtcW, 1280); assert_ne!(layer.property_hash(), before);
}
#[test]
fn property_names_round_trip() {
for tag in PropTag::ALL {
assert_eq!(
PropTag::from_name(tag.name()),
Some(tag),
"{} must round-trip",
tag.name()
);
}
assert_eq!(PropTag::from_name("NOT_A_PROPERTY"), None);
assert_eq!(PropTag::from_name(""), None);
}
#[test]
fn set_property_by_name_reports_unknown_names() {
let mut layer = Layer::new();
assert!(layer.set_property_by_name("CRTC_W", 640));
assert_eq!(layer.property(PropTag::CrtcW), Some(640));
assert!(
!layer.set_property_by_name("NOT_A_PROPERTY", 1),
"an unknown name is reported rather than silently dropped"
);
assert_eq!(layer.property_count(), 1);
}
#[test]
fn properties_iterate_in_tag_order_and_only_when_set() {
let mut layer = Layer::new();
layer
.set_property(PropTag::Zpos, 3)
.set_property(PropTag::CrtcId, 1)
.set_property(PropTag::FbId, 7);
let seen: Vec<PropTag> = layer.properties().map(|(tag, _)| tag).collect();
assert_eq!(seen, vec![PropTag::FbId, PropTag::CrtcId, PropTag::Zpos]);
assert_eq!(layer.property_count(), 3);
}
#[test]
fn snapshot_captures_the_property_state() {
let layer = placed_layer();
let snapshot = layer.snapshot();
assert_eq!(snapshot.get(PropTag::CrtcW), Some(1920));
assert_eq!(snapshot.get(PropTag::Rotation), None);
let mut layer = layer;
layer.set_property(PropTag::CrtcW, 640);
assert_eq!(snapshot.get(PropTag::CrtcW), Some(1920));
assert_eq!(layer.property(PropTag::CrtcW), Some(640));
}
#[test]
fn requires_scaling_compares_fixed_point_source_to_whole_pixel_destination() {
let mut layer = Layer::new();
layer
.set_property(PropTag::SrcW, 1920 << 16)
.set_property(PropTag::SrcH, 1080 << 16)
.set_property(PropTag::CrtcW, 1920)
.set_property(PropTag::CrtcH, 1080);
assert!(!layer.requires_scaling(), "1:1 is not scaling");
layer.set_property(PropTag::CrtcW, 1280);
assert!(layer.requires_scaling());
let mut partial = Layer::new();
partial.set_property(PropTag::SrcW, 1920 << 16);
assert!(!partial.requires_scaling());
}
#[test]
fn geometry_accessors_default_sensibly() {
let empty = Layer::new();
assert_eq!(empty.width(), 0);
assert_eq!(empty.height(), 0);
assert_eq!(empty.modifier(), 0, "unset modifier reads as LINEAR");
assert_eq!(empty.rotation(), 0);
assert_eq!(empty.format(), None);
assert_eq!(empty.crtc_rect(), Rect::default());
let layer = placed_layer();
assert_eq!(
layer.crtc_rect(),
Rect {
x: 0,
y: 0,
w: 1920,
h: 1080
}
);
assert_eq!(layer.format(), Some(fourcc::XRGB8888));
}
#[test]
fn composition_flags_are_independent() {
let mut layer = Layer::new();
assert!(!layer.needs_composition());
layer.set_force_composited(true);
assert!(layer.needs_composition());
assert!(!layer.is_transient_composited());
layer.set_force_composited(false);
layer.set_transient_composited(true);
assert!(
layer.needs_composition(),
"the scene's per-commit flag also forces composition"
);
layer.set_transient_composited(false);
assert!(!layer.needs_composition());
}
#[test]
fn pinned_and_externally_bound_are_distinct() {
let mut layer = placed_layer();
assert!(!layer.is_pinned());
assert!(!layer.is_externally_bound());
layer.set_pinned(true);
assert!(layer.is_pinned());
assert!(!layer.is_externally_bound());
assert_eq!(
layer.property(PropTag::FbId),
Some(42),
"a pinned layer keeps its FB_ID"
);
layer.set_externally_bound(true);
assert!(layer.is_pinned() && layer.is_externally_bound());
}
#[test]
fn dirty_tracking_follows_mutation() {
let mut layer = Layer::new();
assert!(layer.is_dirty(), "a fresh layer is dirty");
layer.mark_clean();
assert!(!layer.is_dirty());
layer.set_property(PropTag::CrtcX, 1);
assert!(layer.is_dirty());
}
fn plane(id: u32, plane_type: PlaneType, crtcs: u32, formats: &[u32]) -> PlaneCapabilities {
PlaneCapabilities {
id,
possible_crtcs: crtcs,
plane_type,
formats: formats.to_vec(),
..PlaneCapabilities::default()
}
}
#[test]
fn from_capabilities_synthesizes_linear_when_in_formats_is_absent() {
let registry = PlaneRegistry::from_capabilities(vec![plane(
31,
PlaneType::Primary,
0b1,
&[fourcc::XRGB8888, fourcc::ARGB8888],
)]);
let plane = registry.by_id(31).expect("plane 31");
assert_eq!(
plane.format_table.len(),
2,
"each advertised format gets a LINEAR entry"
);
assert!(
plane
.format_table
.supports(fourcc::XRGB8888, Modifier::LINEAR)
);
assert!(!plane.format_table.supports(fourcc::NV12, Modifier::LINEAR));
assert_eq!(plane.distinct_modifier_count(), 1, "just LINEAR");
}
#[test]
fn from_capabilities_keeps_an_explicit_format_table() {
let afbc = Modifier(mod_code(vendor::ARM, 1)); let mut caps = plane(31, PlaneType::Overlay, 0b1, &[fourcc::XRGB8888]);
caps.format_table = FormatTable::from_pairs([
(fourcc::XRGB8888, Modifier::LINEAR),
(fourcc::XRGB8888, afbc),
]);
caps.has_format_modifiers = true;
let registry = PlaneRegistry::from_capabilities(vec![caps]);
let plane = registry.by_id(31).expect("plane 31");
assert_eq!(plane.format_table.len(), 2, "the explicit table is kept");
assert!(plane.format_table.supports(fourcc::XRGB8888, afbc));
assert_eq!(plane.distinct_modifier_count(), 2);
}
#[test]
fn bandwidth_class_is_precomputed_and_falls_back_for_unknown_modifiers() {
let afbc = Modifier(mod_code(vendor::ARM, 1));
let interleaved = ARM_16X16_BLOCK_U_INTERLEAVED;
assert_ne!(afbc, interleaved);
let mut caps = plane(31, PlaneType::Overlay, 0b1, &[]);
caps.format_table = FormatTable::from_pairs([
(fourcc::XRGB8888, Modifier::LINEAR),
(fourcc::XRGB8888, afbc),
(fourcc::XRGB8888, interleaved),
]);
let registry = PlaneRegistry::from_capabilities(vec![caps]);
let plane = registry.by_id(31).expect("plane 31");
assert_eq!(plane.bandwidth_class(0), BandwidthClass::Linear);
assert_eq!(plane.bandwidth_class(afbc.0), BandwidthClass::Compression);
assert_eq!(plane.bandwidth_class(interleaved.0), BandwidthClass::Tiling);
let qcom = Modifier(mod_code(vendor::QCOM, 1));
assert_eq!(
plane.bandwidth_class(qcom.0),
BandwidthClass::Compression,
"an unadvertised modifier is classified on the spot"
);
}
#[test]
fn for_crtc_filters_by_the_possible_crtcs_mask() {
let registry = PlaneRegistry::from_capabilities(vec![
plane(31, PlaneType::Primary, 0b001, &[fourcc::XRGB8888]),
plane(32, PlaneType::Overlay, 0b011, &[fourcc::XRGB8888]),
plane(33, PlaneType::Overlay, 0b110, &[fourcc::XRGB8888]),
]);
let ids = |crtc| -> Vec<u32> { registry.for_crtc(crtc).map(|p| p.id).collect() };
assert_eq!(ids(0), vec![31, 32]);
assert_eq!(ids(1), vec![32, 33]);
assert_eq!(ids(2), vec![33]);
assert!(ids(3).is_empty());
}
#[test]
fn compatible_with_crtc_rejects_out_of_range_indices() {
let caps = plane(31, PlaneType::Primary, u32::MAX, &[]);
assert!(caps.compatible_with_crtc(31));
assert!(
!caps.compatible_with_crtc(32),
"index 32 has no bit in a 32-bit mask; shifting by it would be UB in C"
);
assert!(!caps.compatible_with_crtc(u32::MAX));
}
#[test]
fn registry_lookups() {
let registry = PlaneRegistry::from_capabilities(vec![
plane(31, PlaneType::Primary, 0b1, &[fourcc::XRGB8888]),
plane(32, PlaneType::Cursor, 0b1, &[fourcc::ARGB8888]),
]);
assert_eq!(registry.len(), 2);
assert!(!registry.is_empty());
assert_eq!(
registry.by_id(32).map(|p| p.plane_type),
Some(PlaneType::Cursor)
);
assert!(registry.by_id(99).is_none());
assert!(PlaneRegistry::new().is_empty());
}
#[test]
fn supports_format_consults_the_bare_format_list() {
let caps = plane(
31,
PlaneType::Primary,
0b1,
&[fourcc::XRGB8888, fourcc::NV12],
);
assert!(caps.supports_format(fourcc::XRGB8888));
assert!(caps.supports_format(fourcc::NV12));
assert!(!caps.supports_format(fourcc::ARGB8888));
}
#[test]
fn scans_out_reads_the_layout_from_in_formats() {
let afbc = Modifier(0x0800_0000_0000_0001);
let cluster = PlaneCapabilities {
format_table: FormatTable::from_pairs([(fourcc::ARGB8888, afbc)]),
..plane(96, PlaneType::Overlay, 0b1, &[fourcc::ARGB8888])
};
assert!(
cluster.supports_format(fourcc::ARGB8888),
"the bare list says yes"
);
assert!(cluster.scans_out(fourcc::ARGB8888, afbc));
assert!(!cluster.scans_out_linear(fourcc::ARGB8888));
assert!(!cluster.scans_out(fourcc::ARGB8888, Modifier::INVALID));
let bare = plane(56, PlaneType::Primary, 0b1, &[fourcc::ARGB8888]);
assert!(bare.scans_out_linear(fourcc::ARGB8888));
assert!(!bare.scans_out_linear(fourcc::NV12));
}
#[test]
fn matcher_finds_a_perfect_matching() {
let mut matching = BipartiteMatching::with_shape(3, 3);
matching.add_edge(0, 0);
matching.add_edge(0, 1);
matching.add_edge(1, 1);
matching.add_edge(1, 2);
matching.add_edge(2, 2);
assert_eq!(matching.solve(), 3);
for u in 0..3 {
assert!(matching.match_for_left(u).is_some());
}
}
#[test]
fn matcher_handles_contention() {
let mut matching = BipartiteMatching::with_shape(2, 1);
matching.add_edge(0, 0);
matching.add_edge(1, 0);
assert_eq!(matching.solve(), 1);
let placed = usize::from(matching.match_for_left(0).is_some())
+ usize::from(matching.match_for_left(1).is_some());
assert_eq!(placed, 1);
assert!(matching.match_for_right(0).is_some());
}
#[test]
fn matcher_with_no_edges_places_nothing() {
let mut matching = BipartiteMatching::with_shape(4, 4);
assert_eq!(matching.solve(), 0);
assert_eq!(matching.match_for_left(0), None);
assert_eq!(matching.matched_count(), 0);
}
#[test]
fn matcher_prefers_the_higher_scored_plane() {
let mut matching = BipartiteMatching::with_shape(1, 2);
matching.add_scored_edge(0, 0, 10);
matching.add_scored_edge(0, 1, 100);
assert_eq!(matching.solve(), 1);
assert_eq!(
matching.match_for_left(0),
Some(1),
"the higher-scored plane should win when both are free"
);
}
#[test]
fn matcher_gives_up_a_preference_to_keep_cardinality() {
let mut matching = BipartiteMatching::with_shape(2, 2);
matching.add_scored_edge(0, 0, 1000);
matching.add_scored_edge(0, 1, 0);
matching.add_scored_edge(1, 0, 0);
assert_eq!(
matching.solve(),
2,
"both layers must be placed even though it costs layer 0 its preference"
);
assert_eq!(matching.match_for_left(1), Some(0));
assert_eq!(matching.match_for_left(0), Some(1));
}
#[test]
fn matcher_ignores_out_of_range_edges() {
let mut matching = BipartiteMatching::with_shape(2, 2);
matching.add_edge(0, 0);
matching.add_edge(5, 0); matching.add_edge(0, 9);
assert_eq!(matching.solve(), 1);
assert_eq!(matching.match_for_left(0), Some(0));
assert_eq!(matching.match_for_left(5), None);
}
#[test]
fn matcher_reset_reshapes_and_clears() {
let mut matching = BipartiteMatching::with_shape(2, 2);
matching.add_edge(0, 0);
matching.add_edge(1, 1);
assert_eq!(matching.solve(), 2);
matching.reset(1, 3);
assert_eq!(matching.left_len(), 1);
assert_eq!(matching.right_len(), 3);
assert_eq!(
matching.solve(),
0,
"edges from the previous shape are gone"
);
matching.add_edge(0, 2);
assert_eq!(matching.solve(), 1);
assert_eq!(matching.match_for_left(0), Some(2));
}
fn scoring_plane(id: u32, plane_type: PlaneType) -> PlaneCapabilities {
let mut caps = plane(id, plane_type, 0b1, &[fourcc::XRGB8888]);
caps.build_format_metadata();
caps
}
fn scoring_layer() -> Layer {
let mut layer = Layer::new();
layer
.set_property(PropTag::PixelFormat, u64::from(fourcc::XRGB8888))
.set_property(PropTag::CrtcW, 1920)
.set_property(PropTag::CrtcH, 1080)
.set_property(PropTag::SrcW, 1920 << 16)
.set_property(PropTag::SrcH, 1080 << 16);
layer
}
#[test]
fn bandwidth_class_bonus_ranks_compression_highest() {
assert_eq!(bandwidth_class_bonus(BandwidthClass::Compression), 2);
assert_eq!(bandwidth_class_bonus(BandwidthClass::Tiling), 1);
assert_eq!(bandwidth_class_bonus(BandwidthClass::Linear), 0);
}
#[test]
fn cost_bias_buckets_by_frame_size() {
const MB: u64 = 1024 * 1024;
assert_eq!(cost_bias(0), 0);
assert_eq!(cost_bias(MB - 1), 0);
assert_eq!(cost_bias(MB), 1);
assert_eq!(cost_bias(4 * MB), 2);
assert_eq!(cost_bias(16 * MB), 3);
assert_eq!(cost_bias(u64::MAX), 3, "bounded above");
}
#[test]
fn layer_priority_ranks_video_above_animated_above_static() {
let mut video = Layer::new();
video.set_content_type(ContentType::Video);
assert_eq!(layer_priority(&video), 100);
let mut fast = Layer::new();
fast.set_update_hint(60);
assert_eq!(layer_priority(&fast), 80);
let mut slow = Layer::new();
slow.set_update_hint(10);
assert_eq!(layer_priority(&slow), 50);
assert_eq!(layer_priority(&Layer::new()), 10, "static default");
}
#[test]
fn keep_priority_lets_content_class_dominate_app_priority() {
let mut video = Layer::new();
video
.set_content_type(ContentType::Video)
.set_app_priority(0);
let mut generic = Layer::new();
generic.set_app_priority(u8::MAX);
assert!(
keep_priority(&video) > keep_priority(&generic),
"a maximum app_priority must not outrank a higher content class"
);
let mut low = Layer::new();
low.set_content_type(ContentType::Video).set_app_priority(1);
assert!(keep_priority(&video) < keep_priority(&low));
}
#[test]
fn stability_bonus_outweighs_every_structural_term() {
let plane = scoring_plane(31, PlaneType::Primary);
let mut layer = scoring_layer();
layer.set_content_type(ContentType::Video);
layer.set_property(PropTag::Zpos, 0);
let cold = score_pair(&plane, &layer, ScoreContext::default());
let warm = score_pair(
&plane,
&layer,
ScoreContext {
held_last_frame: true,
failure_hits: 0,
},
);
assert_eq!(warm - cold, WARM_STABILITY_BONUS);
assert!(
WARM_STABILITY_BONUS > cold,
"the bonus ({WARM_STABILITY_BONUS}) must exceed the best structural score ({cold}), \
or a newly attractive plane could displace a stable placement"
);
}
#[test]
fn failure_hits_reduce_the_score() {
let plane = scoring_plane(31, PlaneType::Overlay);
let layer = scoring_layer();
let clean = score_pair(&plane, &layer, ScoreContext::default());
let punished = score_pair(
&plane,
&layer,
ScoreContext {
held_last_frame: false,
failure_hits: 5,
},
);
assert_eq!(clean - punished, 5);
}
#[test]
fn composition_layer_prefers_the_primary_plane() {
let primary = scoring_plane(31, PlaneType::Primary);
let overlay = scoring_plane(32, PlaneType::Overlay);
let mut layer = scoring_layer();
layer.set_composition_layer(true);
assert!(
score_pair(&primary, &layer, ScoreContext::default())
> score_pair(&overlay, &layer, ScoreContext::default()),
"the composition output belongs on primary"
);
}
#[test]
fn layer_at_primary_min_zpos_prefers_primary() {
let mut primary = scoring_plane(31, PlaneType::Primary);
primary.zpos_min = Some(0);
primary.zpos_max = Some(0);
let mut overlay = scoring_plane(32, PlaneType::Overlay);
overlay.zpos_min = Some(1);
overlay.zpos_max = Some(4);
let mut layer = scoring_layer();
layer.set_property(PropTag::Zpos, 0);
assert!(
score_pair(&primary, &layer, ScoreContext::default())
> score_pair(&overlay, &layer, ScoreContext::default())
);
}
#[test]
fn zpos_convention_without_a_kernel_zpos_property() {
let primary = scoring_plane(31, PlaneType::Primary); let overlay = scoring_plane(32, PlaneType::Overlay);
let mut bottom = scoring_layer();
bottom.set_property(PropTag::Zpos, 0);
assert!(
score_pair(&primary, &bottom, ScoreContext::default())
> score_pair(&overlay, &bottom, ScoreContext::default()),
"zpos 0 means bottom-most, which is primary"
);
let mut above = scoring_layer();
above.set_property(PropTag::Zpos, 1);
assert!(
score_pair(&overlay, &above, ScoreContext::default())
> score_pair(&primary, &above, ScoreContext::default()),
"a positive zpos means above primary, which is an overlay"
);
}
#[test]
fn statically_compatible_accepts_a_plain_layer() {
let plane = scoring_plane(31, PlaneType::Overlay);
let layer = scoring_layer();
assert!(plane_statically_compatible(&plane, &layer, 0));
}
#[test]
fn a_layer_without_a_format_is_never_compatible() {
let plane = scoring_plane(31, PlaneType::Overlay);
let mut layer = Layer::new();
layer
.set_property(PropTag::CrtcW, 1920)
.set_property(PropTag::CrtcH, 1080);
assert!(
!plane_statically_compatible(&plane, &layer, 0),
"no format means no buffer this frame; placing it would report the \
layer as both assigned and skipped"
);
}
#[test]
fn composited_layers_are_incompatible_with_every_plane() {
let plane = scoring_plane(31, PlaneType::Overlay);
let mut forced = scoring_layer();
forced.set_force_composited(true);
assert!(!plane_statically_compatible(&plane, &forced, 0));
let mut transient = scoring_layer();
transient.set_transient_composited(true);
assert!(!plane_statically_compatible(&plane, &transient, 0));
}
#[test]
fn rotation_requires_the_exact_bits() {
let mut plane = scoring_plane(31, PlaneType::Overlay);
plane.supports_rotation = true;
plane.rotation_bits = 0b0001 | 0b0100;
let mut layer = scoring_layer();
layer.set_property(PropTag::Rotation, 0b0001);
assert!(plane_statically_compatible(&plane, &layer, 0));
layer.set_property(PropTag::Rotation, 0b0010); assert!(
!plane_statically_compatible(&plane, &layer, 0),
"advertising a rotation property is not the same as implementing 90 degrees"
);
}
#[test]
fn scaling_requires_a_scaling_plane() {
let mut plane = scoring_plane(31, PlaneType::Overlay);
plane.supports_scaling = false;
let mut layer = scoring_layer();
layer.set_property(PropTag::CrtcW, 1280); assert!(layer.requires_scaling());
assert!(!plane_statically_compatible(&plane, &layer, 0));
plane.supports_scaling = true;
assert!(plane_statically_compatible(&plane, &layer, 0));
}
#[test]
fn a_zpos_outside_the_planes_range_does_not_rule_it_out() {
let mut plane = scoring_plane(31, PlaneType::Overlay);
plane.zpos_min = Some(2);
plane.zpos_max = Some(4);
let mut layer = scoring_layer();
for zpos in [1, 3, 5, 99] {
layer.set_property(PropTag::Zpos, zpos);
assert!(
plane_statically_compatible(&plane, &layer, 0),
"zpos {zpos}"
);
}
}
#[test]
fn cursor_planes_enforce_their_maximum_size() {
let mut plane = scoring_plane(31, PlaneType::Cursor);
plane.cursor_max_w = 64;
plane.cursor_max_h = 64;
let mut layer = scoring_layer();
layer
.set_property(PropTag::CrtcW, 64)
.set_property(PropTag::CrtcH, 64);
layer
.set_property(PropTag::SrcW, 64 << 16)
.set_property(PropTag::SrcH, 64 << 16);
assert!(plane_statically_compatible(&plane, &layer, 0));
layer
.set_property(PropTag::CrtcW, 128)
.set_property(PropTag::SrcW, 128 << 16);
assert!(!plane_statically_compatible(&plane, &layer, 0));
}
#[test]
fn a_wrong_crtc_is_incompatible() {
let plane = scoring_plane(31, PlaneType::Overlay); let layer = scoring_layer();
assert!(plane_statically_compatible(&plane, &layer, 0));
assert!(!plane_statically_compatible(&plane, &layer, 1));
}
#[expect(
clippy::cast_sign_loss,
reason = "KMS carries CRTC_X/Y as signed values in u64 property slots; the \
round trip through the property bag is exact"
)]
fn at(x: i32, y: i32, w: u32, h: u32) -> Layer {
let mut layer = Layer::new();
layer
.set_property(PropTag::CrtcX, x as u64)
.set_property(PropTag::CrtcY, y as u64)
.set_property(PropTag::CrtcW, u64::from(w))
.set_property(PropTag::CrtcH, u64::from(h));
layer
}
#[test]
fn rects_intersect_is_half_open() {
let a = Rect {
x: 0,
y: 0,
w: 10,
h: 10,
};
assert!(rects_intersect(
a,
Rect {
x: 9,
y: 9,
w: 10,
h: 10
}
));
assert!(
!rects_intersect(
a,
Rect {
x: 10,
y: 0,
w: 10,
h: 10
}
),
"edge-adjacent rectangles do not overlap"
);
assert!(!rects_intersect(
a,
Rect {
x: 0,
y: 10,
w: 10,
h: 10
}
));
assert!(rects_intersect(
a,
Rect {
x: 5,
y: 5,
w: 0,
h: 0
}
));
}
#[test]
fn rects_intersect_does_not_wrap_near_the_coordinate_limit() {
let a = Rect {
x: i32::MAX - 5,
y: 0,
w: 100,
h: 10,
};
let b = Rect {
x: i32::MAX - 1,
y: 0,
w: 100,
h: 10,
};
assert!(rects_intersect(a, b));
}
#[test]
fn split_separates_non_overlapping_layers() {
let left = at(0, 0, 100, 100);
let right = at(200, 0, 100, 100);
let layers = [&left, &right];
let groups = split_independent_groups(&layers);
assert_eq!(groups.len(), 2);
assert_eq!(groups[0], vec![0]);
assert_eq!(groups[1], vec![1]);
}
#[test]
fn split_joins_overlapping_layers() {
let a = at(0, 0, 100, 100);
let b = at(50, 50, 100, 100);
let c = at(500, 500, 10, 10);
let layers = [&a, &b, &c];
let groups = split_independent_groups(&layers);
assert_eq!(groups.len(), 2);
assert_eq!(groups[0], vec![0, 1], "a and b overlap");
assert_eq!(groups[1], vec![2]);
}
#[test]
fn split_is_transitive() {
let a = at(0, 0, 100, 100);
let b = at(90, 0, 100, 100);
let c = at(180, 0, 100, 100);
let layers = [&a, &b, &c];
let groups = split_independent_groups(&layers);
assert_eq!(groups.len(), 1);
assert_eq!(groups[0], vec![0, 1, 2]);
}
#[test]
fn split_group_order_is_deterministic() {
let a = at(300, 0, 50, 50);
let b = at(0, 0, 50, 50);
let c = at(150, 0, 50, 50);
let layers = [&a, &b, &c];
for _ in 0..32 {
let groups = split_independent_groups(&layers);
assert_eq!(
groups,
vec![vec![0], vec![1], vec![2]],
"groups must come back ordered by lowest member index, every time"
);
}
}
#[test]
fn split_handles_empty_and_single_inputs() {
assert!(split_independent_groups(&[]).is_empty());
let only = at(0, 0, 10, 10);
assert_eq!(split_independent_groups(&[&only]), vec![vec![0]]);
}
#[test]
fn split_puts_the_highest_priority_group_first() {
let low = at(0, 0, 50, 50);
let also_low = at(100, 0, 50, 50);
let mut high = at(200, 0, 50, 50);
high.set_app_priority(200);
let layers = [&low, &also_low, &high];
assert_eq!(
split_independent_groups(&layers),
vec![vec![2], vec![0], vec![1]]
);
}
fn overlaps(edges: &[(i32, i32)]) -> impl Fn(&i32, &i32) -> bool + '_ {
move |a, b| {
edges
.iter()
.any(|&(x, y)| (x, y) == (*a, *b) || (y, x) == (*a, *b))
}
}
fn values(items: &[i32], groups: &[Vec<usize>]) -> Vec<Vec<i32>> {
groups
.iter()
.map(|group| group.iter().map(|&i| items[i]).collect())
.collect()
}
#[test]
fn layer_groups_overlap_chains_form_one_group() {
let items = [0, 1, 2, 3];
let groups = independent_groups(&items, overlaps(&[(0, 2), (2, 3)]), |_| 0);
assert_eq!(values(&items, &groups), vec![vec![0, 2, 3], vec![1]]);
}
#[test]
fn layer_groups_ties_keep_input_order() {
let items = [5, 4, 3, 2, 1, 0];
let groups = independent_groups(&items, overlaps(&[(4, 0)]), |_| 0);
assert_eq!(
values(&items, &groups),
vec![vec![5], vec![4, 0], vec![3], vec![2], vec![1]]
);
}
#[test]
fn layer_groups_highest_priority_group_first() {
let items = [0, 1, 2, 3];
let groups = independent_groups(&items, overlaps(&[(1, 3)]), |&item| {
if item == 3 { 100 } else { 10 }
});
assert_eq!(values(&items, &groups), vec![vec![1, 3], vec![0], vec![2]]);
}
#[test]
fn layer_groups_empty_input() {
let items: [i32; 0] = [];
assert!(independent_groups(&items, overlaps(&[]), |_| 0).is_empty());
}
#[test]
fn output_designates_a_composition_layer() {
let mut output = Output::new(42);
assert_eq!(output.crtc_id(), 42);
assert!(output.is_empty());
assert_eq!(output.composition_layer(), None);
let canvas = output.add_layer();
assert!(output.set_composition_layer(canvas));
assert_eq!(output.composition_layer(), Some(canvas));
assert!(output.layer(canvas).expect("canvas").is_composition_layer());
}
#[test]
fn output_adds_and_removes_layers() {
let mut output = Output::new(1);
let a = output.add_layer();
let b = output.add_layer();
assert_eq!(output.len(), 2);
assert!(output.remove_layer(a));
assert_eq!(output.len(), 1);
assert!(output.layer(a).is_none());
assert!(output.layer(b).is_some());
assert!(
!output.remove_layer(a),
"removing twice is not an error, just false"
);
}
#[test]
fn output_never_reuses_layer_ids() {
let mut output = Output::new(1);
let first = output.add_layer();
output.remove_layer(first);
let second = output.add_layer();
assert_ne!(first, second, "a removed id must not come back");
}
#[test]
fn removing_the_composition_layer_clears_the_designation() {
let mut output = Output::new(1);
let canvas = output.add_layer();
output.set_composition_layer(canvas);
output.remove_layer(canvas);
assert_eq!(output.composition_layer(), None);
}
#[test]
fn set_composition_layer_moves_the_flag() {
let mut output = Output::new(1);
let first = output.add_layer();
let second = output.add_layer();
output.set_composition_layer(first);
assert!(output.layer(first).unwrap().is_composition_layer());
output.set_composition_layer(second);
assert!(
!output.layer(first).unwrap().is_composition_layer(),
"the previous composition layer must lose the flag"
);
assert!(output.layer(second).unwrap().is_composition_layer());
assert!(
!output.set_composition_layer(LayerId(999)),
"a layer this output does not own is rejected"
);
}
#[test]
fn output_tracks_dirty_layers() {
let mut output = Output::new(1);
let a = output.add_layer();
let b = output.add_layer();
assert!(output.any_layer_dirty(), "freshly added layers are dirty");
output.mark_clean();
assert!(!output.any_layer_dirty());
assert_eq!(output.changed_layers().count(), 0);
output.layer_mut(b).unwrap().set_property(PropTag::FbId, 7);
assert!(output.any_layer_dirty());
let changed: Vec<_> = output.changed_layers().map(|entry| entry.id).collect();
assert_eq!(changed, vec![b], "only the layer that changed");
assert!(output.layer(a).unwrap().property(PropTag::FbId).is_none());
}
#[test]
fn output_sorts_layers_by_zpos() {
let mut output = Output::new(1);
let top = output.add_layer();
let bottom = output.add_layer();
let middle = output.add_layer();
output
.layer_mut(top)
.unwrap()
.set_property(PropTag::Zpos, 9);
output
.layer_mut(bottom)
.unwrap()
.set_property(PropTag::Zpos, 0);
output
.layer_mut(middle)
.unwrap()
.set_property(PropTag::Zpos, 4);
output.sort_layers_by_zpos();
let order: Vec<_> = output.layers().map(|entry| entry.id).collect();
assert_eq!(order, vec![bottom, middle, top]);
}
#[test]
fn zpos_sort_treats_unset_as_zero_and_is_stable() {
let mut output = Output::new(1);
let first_unset = output.add_layer();
let second_unset = output.add_layer();
let above = output.add_layer();
output
.layer_mut(above)
.unwrap()
.set_property(PropTag::Zpos, 1);
output.sort_layers_by_zpos();
let order: Vec<_> = output.layers().map(|entry| entry.id).collect();
assert_eq!(
order,
vec![first_unset, second_unset, above],
"unset sorts as 0, and equal values keep insertion order"
);
}
#[test]
fn test_cache_does_not_count_successes_as_failures() {
let mut cache = TestCache::new();
for _ in 0..5 {
cache.record(97, 0x1234, true);
}
assert_eq!(cache.failure_count(97, 0x1234), 0);
assert_eq!(cache.lookup(97, 0x1234), Some(true));
}
#[test]
fn test_cache_failures_accumulate() {
let mut cache = TestCache::new();
cache.record(100, 0x1234, false);
assert_eq!(cache.failure_count(100, 0x1234), 1);
cache.record(100, 0x1234, false);
assert_eq!(cache.failure_count(100, 0x1234), 2);
assert_eq!(cache.lookup(100, 0x1234), Some(false));
}
#[test]
fn test_cache_latest_verdict_wins() {
let mut cache = TestCache::new();
cache.record(97, 0x1234, false);
cache.record(97, 0x1234, false);
assert_eq!(cache.failure_count(97, 0x1234), 2);
cache.record(97, 0x1234, true);
assert_eq!(cache.failure_count(97, 0x1234), 0);
assert_eq!(cache.lookup(97, 0x1234), Some(true));
}
#[test]
fn test_cache_unknown_is_not_a_failure() {
let cache = TestCache::new();
assert_eq!(cache.failure_count(999, 0x1), 0);
assert_eq!(cache.lookup(999, 0x1), None);
}
const ALPHA_FULL: u64 = 0xFFFF;
#[test]
fn plane_alpha_full_range_is_identity() {
for value in [0, 0x4000, 0x8000, ALPHA_FULL] {
assert_eq!(rescale_alpha(value, ALPHA_FULL), value);
}
}
#[test]
fn plane_alpha_full_opacity_maps_onto_the_advertised_maximum() {
assert_eq!(rescale_alpha(ALPHA_FULL, 255), 255);
assert_eq!(rescale_alpha(ALPHA_FULL, 1023), 1023);
assert_eq!(rescale_alpha(ALPHA_FULL, 1), 1);
}
#[test]
fn plane_alpha_partial_alpha_keeps_its_fraction() {
assert_eq!(rescale_alpha(0x8000, 255), 128);
assert_eq!(rescale_alpha(0x4000, 255), 64);
assert_eq!(rescale_alpha(0x8000, 1023), 512);
assert_eq!(rescale_alpha(0x4000, 1023), 256);
}
#[test]
fn plane_alpha_zero_stays_zero() {
for max in [255, 1023, 1] {
assert_eq!(rescale_alpha(0, max), 0);
}
}
#[test]
fn plane_alpha_oversized_input_saturates() {
assert_eq!(rescale_alpha(0x1_0000, 255), 255);
assert_eq!(rescale_alpha(0xFFFF_FFFF, 255), 255);
assert_eq!(rescale_alpha(0xFFFF_FFFF, ALPHA_FULL), ALPHA_FULL);
}
#[test]
fn plane_alpha_is_monotonic_and_bounded() {
for max in [1, 15, 255, 1023, 4095] {
let mut previous = 0;
for value in (0..=ALPHA_FULL).step_by(97) {
let got = rescale_alpha(value, max);
assert!(got >= previous, "max={max} value={value}");
assert!(got <= max, "max={max} value={value}");
previous = got;
}
}
}
#[test]
fn plane_alpha_respects_each_planes_own_range() {
assert_eq!(rescale_alpha(ALPHA_FULL, 0x7FFF), 0x7FFF);
assert_eq!(rescale_alpha(0x8000, 0x7FFF), 0x4000);
assert_eq!(rescale_alpha(ALPHA_FULL / 2, 1), 0);
assert_eq!(rescale_alpha(ALPHA_FULL, 1), 1);
}
fn zplane(id: u32, plane_type: PlaneType, min: Option<u64>, max: Option<u64>) -> PlaneCapabilities {
PlaneCapabilities {
id,
plane_type,
zpos_min: min,
zpos_max: max,
..PlaneCapabilities::default()
}
}
fn primary_0() -> PlaneCapabilities {
zplane(31, PlaneType::Primary, Some(0), Some(0))
}
fn primary_2() -> PlaneCapabilities {
zplane(40, PlaneType::Primary, Some(2), Some(2))
}
fn mutable_overlay() -> PlaneCapabilities {
zplane(50, PlaneType::Overlay, Some(0), Some(255))
}
fn no_zpos() -> PlaneCapabilities {
zplane(60, PlaneType::Overlay, None, None)
}
#[test]
fn zpos_fixed_only_when_min_equals_max() {
assert!(zpos_fixed(&primary_0()));
assert!(zpos_fixed(&primary_2()));
assert!(!zpos_fixed(&mutable_overlay()));
assert!(!zpos_fixed(&no_zpos()));
}
#[test]
fn effective_zpos_is_the_slot_on_fixed_planes() {
assert_eq!(effective_zpos(&primary_2(), Some(7)), Some(2));
assert_eq!(effective_zpos(&mutable_overlay(), Some(7)), Some(7));
assert_eq!(effective_zpos(&no_zpos(), Some(7)), Some(7));
assert_eq!(effective_zpos(&mutable_overlay(), None), None);
}
#[test]
fn the_bottom_layer_may_take_a_fixed_primary() {
assert!(stacking_consistent(
&primary_2(),
Some(0),
&mutable_overlay(),
Some(3)
));
assert!(stacking_consistent(
&primary_0(),
Some(1),
&mutable_overlay(),
Some(5)
));
}
#[test]
fn an_inverted_stack_is_rejected() {
assert!(!stacking_consistent(
&primary_2(),
Some(5),
&mutable_overlay(),
Some(3)
));
assert!(!stacking_consistent(
&mutable_overlay(),
Some(3),
&primary_2(),
Some(5)
));
}
#[test]
fn different_requests_on_one_slot_are_rejected() {
assert!(!stacking_consistent(
&primary_2(),
Some(5),
&mutable_overlay(),
Some(2)
));
}
#[test]
fn unordered_requests_are_always_consistent() {
assert!(stacking_consistent(
&primary_2(),
None,
&mutable_overlay(),
Some(3)
));
assert!(stacking_consistent(
&primary_2(),
Some(4),
&mutable_overlay(),
Some(4)
));
assert!(stacking_consistent(
&no_zpos(),
Some(9),
&primary_0(),
Some(1)
));
}
#[test]
fn previously_legal_assignments_are_unaffected() {
let overlay = mutable_overlay();
assert!(stacking_consistent(&overlay, Some(1), &overlay, Some(9)));
assert!(stacking_consistent(&overlay, Some(9), &overlay, Some(1)));
assert!(stacking_consistent(
&primary_2(),
Some(2),
&overlay,
Some(5)
));
assert!(stacking_consistent(
&primary_0(),
Some(0),
&overlay,
Some(1)
));
}
fn sde(id: u32) -> PlaneCapabilities {
zplane(id, PlaneType::Overlay, Some(0), Some(10))
}
fn written(entries: &[StackEntry<'_>]) -> Vec<u64> {
entries
.iter()
.map(|e| e.written.unwrap_or(u64::MAX))
.collect()
}
#[test]
fn the_stack_packs_armed_planes_densely() {
let (a, b, c) = (sde(97), sde(115), sde(118));
let mut e = [
StackEntry::new(&b, Some(8)),
StackEntry::new(&a, Some(0)),
StackEntry::new(&c, Some(5)),
];
assert!(stack_zpos(&mut e));
assert_eq!(written(&e), vec![2, 0, 1]);
}
#[test]
fn the_stack_keeps_a_fixed_slot() {
let (primary, overlay) = (primary_2(), mutable_overlay());
let mut e = [
StackEntry::new(&primary, Some(2)),
StackEntry::new(&overlay, Some(9)),
StackEntry::new(&overlay, Some(1)),
];
assert!(stack_zpos(&mut e));
assert_eq!(written(&e), vec![2, 3, 0]);
}
#[test]
fn the_stack_separates_ties() {
let (b, c) = (sde(115), sde(118));
let mut e = [StackEntry::new(&c, Some(4)), StackEntry::new(&b, Some(4))];
assert!(stack_zpos(&mut e));
assert_eq!(written(&e), vec![1, 0]);
}
#[test]
fn the_stack_puts_below_ties_under_equal_requests() {
let (a, b, c) = (sde(97), sde(115), sde(118));
let mut slot = StackEntry::new(&c, Some(7));
slot.below_ties = true;
let mut e = [
StackEntry::new(&b, Some(3)),
StackEntry::new(&a, Some(7)),
slot,
];
assert!(stack_zpos(&mut e));
assert_eq!(written(&e), vec![0, 2, 1]);
}
#[test]
fn the_stack_honors_each_planes_minimum() {
let (overlay, high) = (
mutable_overlay(),
zplane(70, PlaneType::Overlay, Some(4), Some(7)),
);
let mut e = [
StackEntry::new(&overlay, Some(0)),
StackEntry::new(&high, Some(6)),
];
assert!(stack_zpos(&mut e));
assert_eq!(written(&e), vec![0, 4]);
}
#[test]
fn an_overflowing_stack_keeps_the_requested_values() {
let (overlay, tight) = (
mutable_overlay(),
zplane(71, PlaneType::Overlay, Some(0), Some(0)),
);
let mut e = [
StackEntry::new(&overlay, Some(0)),
StackEntry::new(&tight, Some(5)),
];
assert!(!stack_zpos(&mut e));
assert_eq!(written(&e), vec![0, 5]);
let primary = primary_0();
let mut f = [
StackEntry::new(&overlay, Some(0)),
StackEntry::new(&overlay, Some(1)),
StackEntry::new(&primary, Some(3)),
];
assert!(!stack_zpos(&mut f));
}
#[test]
fn the_stack_skips_what_it_cannot_rank() {
let (none, overlay) = (no_zpos(), mutable_overlay());
let mut e = [
StackEntry::new(&none, Some(3)),
StackEntry::new(&overlay, None),
StackEntry::new(&overlay, Some(9)),
];
assert!(stack_zpos(&mut e));
assert_eq!(e[0].written, Some(3));
assert_eq!(e[1].written, None);
assert_eq!(e[2].written, Some(0));
}
use crate::plane_order::{Choice, PlaneOrder, Position, plane_order_consistent};
#[test]
fn plane_order_leaves_a_canvas_plane_for_the_run() {
let order = PlaneOrder::new(5, 3, |_, _| true);
let hosts = |_: usize| true;
let choice = order
.choose(5, None, Some(&hosts), |_| 1)
.expect("a split exists");
assert_eq!(
choice,
Choice {
run: 0..3,
canvas: Some(0)
}
);
assert_eq!(order.plane_of(3, &choice), Some(1));
assert_eq!(order.plane_of(4, &choice), Some(2));
assert_eq!(order.plane_of(1, &choice), None);
}
#[test]
fn plane_order_without_a_canvas_drops_the_run() {
let order = PlaneOrder::new(5, 3, |_, _| true);
let choice = order.choose(5, None, None, |_| 1).expect("a split exists");
assert_eq!(choice.run.len(), 2);
assert_eq!(choice.canvas, None);
}
#[test]
fn plane_order_runs_cover_the_forced_layers() {
let order = PlaneOrder::new(4, 4, |i, _| i != 3);
let hosts = |_: usize| true;
let choice = order
.choose(4, Some(&(1..2)), Some(&hosts), |i| {
if i == 1 { 1000 } else { 1 }
})
.expect("a split exists");
assert!(
choice.run.contains(&1) && choice.run.contains(&3),
"{choice:?}"
);
}
#[test]
fn plane_order_needs_a_host_between_the_halves() {
let order = PlaneOrder::new(4, 3, |_, _| true);
let hosts = |j: usize| j == 2;
let choice = order
.choose(4, None, Some(&hosts), |_| 1)
.expect("a split exists");
assert_eq!(
choice,
Choice {
run: 2..4,
canvas: Some(2)
}
);
}
const fn stacked_at(zpos: u64, plane: Option<u32>, on_canvas: bool) -> Position {
Position {
zpos,
plane,
on_canvas,
}
}
#[test]
fn plane_order_consistency() {
assert!(plane_order_consistent(&[
stacked_at(0, Some(31), false),
stacked_at(1, Some(32), true),
stacked_at(2, Some(32), true),
stacked_at(3, Some(33), false),
]));
assert!(!plane_order_consistent(&[
stacked_at(1, Some(31), false),
stacked_at(0, Some(32), false),
]));
assert!(!plane_order_consistent(&[
stacked_at(0, Some(32), false),
stacked_at(1, Some(32), true),
]));
assert!(!plane_order_consistent(&[
stacked_at(0, None, true),
stacked_at(1, Some(32), false),
]));
assert!(plane_order_consistent(&[
stacked_at(1, Some(33), false),
stacked_at(1, Some(31), false),
]));
}
#[test]
fn parses_parent_from_capabilities_blob() {
let blob = b"max_linewidth=4096\nprimary_smart_plane_id=118\nmax_upscale=20\n";
assert_eq!(crate::parse_multirect_parent(blob), Some(118));
}
#[test]
fn key_at_blob_start() {
assert_eq!(
crate::parse_multirect_parent(b"primary_smart_plane_id=97\n"),
Some(97)
);
}
#[test]
fn ordinary_plane_has_no_parent() {
assert_eq!(
crate::parse_multirect_parent(b"max_linewidth=4096\nscaler_version=2\n"),
None
);
assert_eq!(crate::parse_multirect_parent(b""), None);
}
#[test]
fn key_must_start_a_line() {
assert_eq!(
crate::parse_multirect_parent(b"not_primary_smart_plane_id=5\n"),
None
);
}
#[test]
fn garbage_or_zero_value_ignored() {
assert_eq!(
crate::parse_multirect_parent(b"primary_smart_plane_id=abc\n"),
None
);
assert_eq!(
crate::parse_multirect_parent(b"primary_smart_plane_id=0\n"),
None
);
}
#[test]
fn virtual_plane_needs_parent_in_use() {
let used = [115_u32, 97];
let in_use = |id: u32| used.contains(&id);
assert!(crate::multirect_pairing_ok(None, in_use), "ordinary plane");
assert!(
crate::multirect_pairing_ok(Some(115), in_use),
"parent armed"
);
assert!(
!crate::multirect_pairing_ok(Some(118), in_use),
"parent free"
);
}
#[test]
fn the_key_is_found_in_bytes_that_are_not_utf8() {
assert_eq!(
crate::parse_multirect_parent(b"name=\xff\xfe\nprimary_smart_plane_id=118\0"),
Some(118)
);
}
#[test]
fn the_sa8155p_capabilities_stack_nine_planes() {
let blob = b"dim_layer_v1_max_layers=7\nhw_version=1342177281\nmax_linewidth=2560\n\
max_blendstages=11\nqseed_type=qseed3\nhas_src_split=1\nsec_ui_blendstage=10\n\
use_baselayer_for_stage=0\n";
assert_eq!(crate::parse_blend_stage_limit(blob), Some(9));
}
#[test]
fn without_a_secure_ui_stage_every_stage_above_the_base_is_usable() {
assert_eq!(
crate::parse_blend_stage_limit(b"max_blendstages=11\n"),
Some(10)
);
assert_eq!(
crate::parse_blend_stage_limit(b"max_blendstages=7\nsec_ui_blendstage=9\n"),
Some(6)
);
}
#[test]
fn a_blob_that_does_not_say_sets_no_limit() {
assert_eq!(crate::parse_blend_stage_limit(b""), None);
assert_eq!(
crate::parse_blend_stage_limit(b"primary_smart_plane_id=97\n"),
None
);
assert_eq!(
crate::parse_blend_stage_limit(b"not_max_blendstages=11\n"),
None,
"only a key at the start of a line counts"
);
assert_eq!(
crate::parse_blend_stage_limit(b"max_blendstages=abc\n"),
None
);
assert_eq!(
crate::parse_blend_stage_limit(b"max_blendstages=1\n"),
None,
"a mixer with only its base stacks nothing"
);
}
#[test]
fn a_registry_reports_each_crtcs_limit() {
let mut registry = PlaneRegistry::new();
assert_eq!(registry.blend_stage_limit(0), None);
registry.set_blend_stage_limit(2, Some(9));
assert_eq!(registry.blend_stage_limit(2), Some(9));
assert_eq!(registry.blend_stage_limit(1), None);
assert_eq!(registry.blend_stage_limit(7), None);
}