0

I have the following struct:

pub struct Restriction {
    pub min: Option<u64>,
    pub max: Option<u64>,
}

for which I have defined Arbitrary as follows:

impl Arbitrary for Restriction {
    fn arbitrary(g: &mut Gen) -> Self {
        let x = Option::<u64>::arbitrary(g);
        let y = Option::<u64>::arbitrary(g);

        let (min, max) = match (x, y) {
            (Some(x_), Some(y_)) if x > y => (Some(y_), Some(x_)),
            vals => vals,
        };

        Restriction { min, max }
    }

    fn shrink(&self) -> Box<dyn Iterator<Item = Self>> {
        Box::new(unfold(self.clone(), |r| {
            let next_min = match r.min {
                None => None,
                Some(0) => Some(None),
                Some(x) => Some(Some(x - 1)),
            };

            let next_max = match r.max {
                None => None,
                Some(0) => Some(None),
                Some(x) => Some(Some(x - 1)),
            };

            match (next_min, next_max) {
                (None, None) => None,
                (None, Some(max)) => {
                    r.max = max;
                    Some(r.clone())
                }
                (Some(min), None) => {
                    r.min = min;
                    Some(r.clone())
                }
                (Some(min), Some(max)) => {
                    r.min = min;
                    r.max = max;
                    Some(r.clone())
                }
            }
        }))
    }
}

I also have this test case:

    #[quickcheck]
    fn restriction_silly(x: Restriction) -> bool {
        let y = Restriction {
            min: None,
            max: None,
        };
        x == y
    }

Without my custom implementation of shrink, the counter example produced is:

Restriction { min: Some(15789104099073884865), max: Some(16586241492943163879) }

Intuitively one can see that a better counter example could be Restriction { min: Some(1), max: None }, I would even accept Restriction { min: Some(1), max: Some(1) }.

Unfortunately when using my custom shrink implementation the test errors with fatal runtime error: stack overflow.

I even wrote the following test, which passes fine, to check that a restriction does in fact shrink to the empty restriction:

    #[test]
    fn restriction_shrink_to_empty() -> Result<(), std::io::Error> {
        let r = Restriction {
            min: Some(3),
            max: Some(7),
        };

        assert_eq!(r.shrink().last(), Some(Restriction::EMPTY));
        Ok(())
    }

So I'm now rather confused as to what is wrong with my shrink implementation.

2
  • Well your shrink implementation will need 15 billion billion iterations to finish. I don't know how quickcheck works, but if it's calling shrink from a recursive function, no wonder you get a stack overflow. Even if it called your function iteratively, you would need a very long time to finish. I suggest you try some faster-converging function (e.g. using x/2 instead of x-1). Commented Jun 21, 2022 at 14:50
  • @Jmb ah perfect, that fixed it. I was so caught up in second guessing whether the iterator wasn't being lazy or something that I hadn't considered the fact that it is just huge! If you'd like to add an answer I can accept it. Commented Jun 21, 2022 at 14:59

1 Answer 1

1

Your shrink implementation will need 15 billion billion iterations to finish. I don't know how quickcheck works, but if it's calling shrink from a recursive function, no wonder you get a stack overflow. Even if it called your function iteratively, you would need a very long time to finish. I suggest you try some faster-converging function (e.g. using x/2 instead of x-1):

fn shrink(&self) -> Box<dyn Iterator<Item = Self>> {
    Box::new(unfold(self.clone(), |r| {
        let next_min = match r.min {
            None => None,
            Some(0) => Some(None),
            Some(x) => Some(Some(x / 2)),
        };

        let next_max = match r.max {
            None => None,
            Some(0) => Some(None),
            Some(x) => Some(Some(x / 2)),
        };

        match (next_min, next_max) {
            (None, None) => None,
            (None, Some(max)) => {
                r.max = max;
                Some(r.clone())
            }
            (Some(min), None) => {
                r.min = min;
                Some(r.clone())
            }
            (Some(min), Some(max)) => {
                r.min = min;
                r.max = max;
                Some(r.clone())
            }
        }
    }))
}
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.