arbitrary.rs
1 //! Utilities for fuzz and/or property testing using `arbitrary`. 2 3 use arbitrary::Unstructured; 4 use kurbo::{CubicBez, Point}; 5 6 use crate::{curve::Quadratic, geom}; 7 8 /// Generate an arbitrary float in some range. 9 pub fn float_in_range( 10 start: f64, 11 end: f64, 12 u: &mut Unstructured<'_>, 13 ) -> Result<f64, arbitrary::Error> { 14 let num: u32 = u.arbitrary()?; 15 let t = num as f64 / u32::MAX as f64; 16 Ok((start + t * (end - start)).clamp(start, end)) 17 } 18 19 fn float(u: &mut Unstructured<'_>) -> Result<f64, arbitrary::Error> { 20 float_in_range(-1e6, 1e6, u) 21 } 22 23 fn float_at_least(min: f64, u: &mut Unstructured<'_>) -> Result<f64, arbitrary::Error> { 24 float_in_range(min, 1e6, u) 25 } 26 27 fn float_at_most(max: f64, u: &mut Unstructured<'_>) -> Result<f64, arbitrary::Error> { 28 float_in_range(-1e6, max, u) 29 } 30 31 /// Generate a float in some range, but give it a chance to be close to another float. 32 fn another_float_in_range( 33 orig: f64, 34 start: f64, 35 end: f64, 36 u: &mut Unstructured<'_>, 37 ) -> Result<f64, arbitrary::Error> { 38 let close: bool = u.arbitrary()?; 39 if close { 40 let ulps: i32 = u.int_in_range(-32..=32)?; 41 let scale = 1.0f64 + ulps as f64 * f64::EPSILON; 42 Ok((orig * scale).clamp(start, end)) 43 } else { 44 float_in_range(start, end, u) 45 } 46 } 47 48 fn point(u: &mut Unstructured<'_>) -> Result<Point, arbitrary::Error> { 49 Ok(Point::new(float(u)?, float(u)?)) 50 } 51 52 fn point_at_least(min: f64, u: &mut Unstructured<'_>) -> Result<Point, arbitrary::Error> { 53 Ok(Point::new(float(u)?, float_at_least(min, u)?)) 54 } 55 56 fn point_at_most(max: f64, u: &mut Unstructured<'_>) -> Result<Point, arbitrary::Error> { 57 Ok(Point::new(float(u)?, float_at_most(max, u)?)) 58 } 59 60 /// Generate an arbitrary cubic Bezier, guaranteed to be monotonically increasing in y. 61 pub fn monotonic_bezier(u: &mut Unstructured<'_>) -> Result<CubicBez, arbitrary::Error> { 62 let p0 = point(u)?; 63 let p3 = point(u)?; 64 let (p0, mut p3) = if p0.y < p3.y { (p0, p3) } else { (p3, p0) }; 65 if p3.y == p0.y { 66 p3.y += 1.0; 67 } 68 69 let p1 = point_at_least(p0.y, u)?; 70 let p2 = point_at_most(p3.y, u)?; 71 72 let ret = CubicBez::new(p0, p1, p2, p3); 73 Ok(geom::monotonic_pieces(ret) 74 .into_iter() 75 .find(|c| c.p0.y < c.p3.y) 76 // unwrap: we started with p0 having smaller y, so there must be 77 // a monotonic component that's increasing in y. 78 .unwrap()) 79 } 80 81 /// Generate an arbitrary cubic Bezier, guaranteed to be monotonically increasing in y. 82 /// 83 /// This generated Bezier has a chance to be "close" to `first`, for example by starting 84 /// at the same point or with the same tangent. 85 pub fn another_monotonic_bezier( 86 u: &mut Unstructured<'_>, 87 first: &CubicBez, 88 ) -> Result<CubicBez, arbitrary::Error> { 89 let same_start: bool = u.arbitrary()?; 90 let same_start_tangent: bool = same_start && u.arbitrary()?; 91 92 let p0 = if same_start { first.p0 } else { point(u)? }; 93 94 let p1 = if same_start_tangent { 95 first.p1 96 } else { 97 point_at_least(p0.y, u)? 98 }; 99 100 let p3 = point_at_least(p0.y.max(first.p0.y), u)?; 101 let p2 = point_at_most(p3.y, u)?; 102 103 let ret = CubicBez::new(p0, p1, p2, p3); 104 Ok(geom::monotonic_pieces(ret) 105 .into_iter() 106 .next() 107 .unwrap_or(ret)) 108 } 109 110 /// Generate an arbitrary quadratic function, with coefficients of roughly the scale `size`. 111 pub fn quadratic(size: f64, u: &mut Unstructured<'_>) -> Result<Quadratic, arbitrary::Error> { 112 let use_coeffs: bool = u.arbitrary()?; 113 if use_coeffs { 114 let c2 = float_in_range(-size, size, u)?; 115 let c1 = another_float_in_range(c2, -size, size, u)?; 116 let c0 = another_float_in_range(c1, -size, size, u)?; 117 118 Ok(Quadratic { c2, c1, c0 }) 119 } else { 120 // Generate the roots, with a bias towards an almost-repeated root. 121 let size = size.sqrt(); 122 123 let r1 = float_in_range(-size, size, u)?; 124 let r2 = another_float_in_range(r1, -size, size, u)?; 125 let scale = float_in_range(-size, size, u)?; 126 127 Ok(Quadratic { 128 c2: scale, 129 c1: -scale * (r1 + r2), 130 c0: scale * r1 * r2, 131 }) 132 } 133 }