Skip to content

Commit

Permalink
impl Arbitrary for Text.
Browse files Browse the repository at this point in the history
This is not quite up to final quality, because there are in fact some
numeric overflow panics. These need to be fixed, but for now I'm
restricting the generated range. Better to document them and start
fuzzing for _other_ problems than to hold off.
  • Loading branch information
kpreid committed Dec 10, 2023
1 parent 5820f21 commit b4ebb67
Show file tree
Hide file tree
Showing 2 changed files with 54 additions and 1 deletion.
11 changes: 10 additions & 1 deletion all-is-cubes/src/block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -733,7 +733,7 @@ mod arbitrary_block {
// Manual impl because `GridPoint` doesn't impl Arbitrary.
impl<'a> Arbitrary<'a> for Primitive {
fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
Ok(match u.int_in_range(0..=3)? {
Ok(match u.int_in_range(0..=4)? {
0 => Primitive::Air,
1 => Primitive::Atom(Atom {
attributes: BlockAttributes::arbitrary(u)?,
Expand All @@ -748,6 +748,11 @@ mod arbitrary_block {
resolution: Resolution::arbitrary(u)?,
space: URef::arbitrary(u)?,
},
4 => Primitive::Text {
text: text::Text::arbitrary(u)?,
// TODO: fix unhandled overflows so this can be full i32 range
offset: GridVector::from(<[i16; 3]>::arbitrary(u)?.map(i32::from)),
},
_ => unreachable!(),
})
}
Expand All @@ -768,6 +773,10 @@ mod arbitrary_block {
Resolution::size_hint(depth),
URef::<Space>::size_hint(depth),
]),
size_hint::and_all(&[
text::Text::size_hint(depth),
<[i16; 3]>::size_hint(depth),
]),
])
})
}
Expand Down
44 changes: 44 additions & 0 deletions all-is-cubes/src/block/text.rs
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,45 @@ impl universe::VisitRefs for Text {
}
}

#[cfg(feature = "arbitrary")]
impl<'a> arbitrary::Arbitrary<'a> for Text {
fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
// As a temporary measure, restrict GridAab coordinate range to avoid overflows.
// Eventually we should fix the overflows, but let's do that after we build our own
// text rendering because that'll be easier.
// (Another possibility would be to actually restrict `layout_bounds` itself,)
let layout_bounds = GridAab::checked_from_lower_upper(
// euclid::Point3D doesn't implement Arbitrary (just an oversight apparently)
<[i16; 3]>::arbitrary(u)?.map(i32::from),
<[u16; 3]>::arbitrary(u)?.map(i32::from),
)
.map_err(|_volume_error| arbitrary::Error::IncorrectFormat)?;

Ok(Self {
// ArcStr doesn't implement Arbitrary
string: alloc::string::String::arbitrary(u)?.into(),
font: Font::arbitrary(u)?,
foreground: Block::arbitrary(u)?,
resolution: Resolution::arbitrary(u)?,
layout_bounds,
positioning: Positioning::arbitrary(u)?,
})
}

fn size_hint(depth: usize) -> (usize, Option<usize>) {
arbitrary::size_hint::recursion_guard(depth, |depth| {
arbitrary::size_hint::and_all(&[
alloc::string::String::size_hint(depth),
Font::size_hint(depth),
Block::size_hint(depth),
Resolution::size_hint(depth),
GridAab::size_hint(depth),
Positioning::size_hint(depth),
])
})
}
}

impl TextBuilder {
/// Converts this builder into a [`Text`] value.
pub fn build(self) -> Text {
Expand Down Expand Up @@ -415,6 +454,7 @@ impl Default for TextBuilder {

/// A font that may be used with [`Text`] blocks.
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[non_exhaustive]
pub enum Font {
/// A font whose characteristics are unspecified, other than that it is general-purpose and
Expand Down Expand Up @@ -449,6 +489,7 @@ impl universe::VisitRefs for Font {

/// How a [`Text`] is to be positioned within a block.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[allow(clippy::exhaustive_structs)] // TODO: probably want to do something else
pub struct Positioning {
/// How to place the text horizontally relative to the anchor point.
Expand All @@ -475,6 +516,7 @@ pub struct Positioning {
///
/// A component of [`Positioning`].
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[non_exhaustive]
pub enum PositioningX {
// TODO: Distinguish 'end of graphic' (last bit of ink) from 'nominal character spacing'?
Expand All @@ -498,6 +540,7 @@ pub enum PositioningX {
///
/// A component of [`Positioning`].
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[non_exhaustive]
pub enum PositioningY {
/// The top of a line of text (past which no voxels extend) is aligned with the top edge
Expand All @@ -523,6 +566,7 @@ pub enum PositioningY {
///
/// A component of [`Positioning`].
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[non_exhaustive]
pub enum PositioningZ {
/// Against the back (negative Z) face of the layout bounds.
Expand Down

0 comments on commit b4ebb67

Please sign in to comment.