Skip to content

Commit 908f113

Browse files
authored
Switch from quickcheck to proptest (#673)
1 parent 241bf5c commit 908f113

File tree

9 files changed

+125
-281
lines changed

9 files changed

+125
-281
lines changed

Cargo.toml

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ exclude = [".github/*", "examples/*", "tests/*"]
1414

1515
[features]
1616
default = ["rayon", "image/default"]
17-
property-testing = ["quickcheck"]
1817
display-window = ["sdl2"]
1918
rayon = ["dep:rayon", "image/rayon"]
2019

@@ -33,7 +32,6 @@ rand = { version = "0.8.5", default-features = false, features = [
3332
] }
3433
rand_distr = { version = "0.4.3", default-features = false }
3534
rayon = { version = "1.8.0", optional = true, default-features = false }
36-
quickcheck = { version = "1.0.3", optional = true, default-features = false }
3735
sdl2 = { version = "0.36", optional = true, default-features = false, features = [
3836
"bundled",
3937
] }
@@ -45,7 +43,6 @@ getrandom = { version = "0.2", default-features = false, features = ["js"] }
4543
[dev-dependencies]
4644
assert_approx_eq = "1.1.0"
4745
proptest = "1.4.0"
48-
quickcheck = "1.0.3"
4946
wasm-bindgen-test = "0.3.38"
5047

5148
[package.metadata.docs.rs]

README.md

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ from not using linear color spaces.
3939
This library provides both single-threaded and multi-threaded variations of several functions
4040
by using [rayon](https://github.com/rayon-rs/rayon).
4141

42-
Depending on image size and the amount of work performed per pixel the parallel versions may not
42+
Depending on image size and the amount of work performed per pixel the parallel versions may not
4343
always be faster - we recommend benchmarking for your specific use-case.
4444

4545
## Crate Features
@@ -52,13 +52,11 @@ always be faster - we recommend benchmarking for your specific use-case.
5252

5353
- `katexit`: enables latex in documentation via
5454
[katexit](https://github.com/termoshtt/katexit)
55-
- `property-testing`: enables `quickcheck`
56-
- `quickcheck`: exposes helper types and methods to enable property testing
57-
via [quickcheck](https://github.com/BurntSushi/quickcheck)
5855
- `display-window`: enables `sdl2`
5956
- `sdl2`: enables the displaying of images (using `imageproc::window`) with
6057
[sdl2](https://github.com/Rust-SDL2/rust-sdl2)
6158

6259
## How to contribute
6360

64-
See [CONTRIBUTING.md](CONTRIBUTING.md).
61+
See [CONTRIBUTING.md](CONTRIBUTING.md).
62+

src/distance_transform.rs

Lines changed: 71 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -443,34 +443,10 @@ fn intersection<S: Source + ?Sized>(f: &S, p: usize, q: usize) -> f64 {
443443
mod tests {
444444
use super::*;
445445
use crate::definitions::Image;
446-
use crate::property_testing::GrayTestImage;
447-
use crate::utils::pixel_diff_summary;
448446
use image::{GrayImage, Luma};
449-
use quickcheck::{quickcheck, Arbitrary, Gen, TestResult};
450447
use std::cmp::max;
451448
use std::f64;
452449

453-
/// Avoid generating garbage floats during certain calculations below.
454-
#[derive(Debug, Clone)]
455-
struct BoundedFloat(f64);
456-
457-
impl Arbitrary for BoundedFloat {
458-
fn arbitrary(g: &mut Gen) -> Self {
459-
let mut f;
460-
461-
loop {
462-
f = f64::arbitrary(g);
463-
464-
if f.is_normal() {
465-
f = f.clamp(-1_000_000.0, 1_000_000.0);
466-
break;
467-
}
468-
}
469-
470-
BoundedFloat(f)
471-
}
472-
}
473-
474450
#[test]
475451
fn test_distance_transform_saturation() {
476452
// A single foreground pixel in the top-left
@@ -486,54 +462,10 @@ mod tests {
486462
assert_pixels_eq!(distances, expected);
487463
}
488464

489-
impl Sink for Vec<f64> {
490-
fn put(&mut self, idx: usize, value: f64) {
491-
self[idx] = value;
492-
}
493-
fn len(&self) -> usize {
494-
self.len()
495-
}
496-
}
497-
498-
fn distance_transform_1d(f: &Vec<f64>) -> Vec<f64> {
499-
let mut r = vec![0.0; f.len()];
500-
let mut e = LowerEnvelope::new(f.len());
501-
distance_transform_1d_mut(f, &mut r, &mut e);
502-
r
503-
}
504-
505-
#[test]
506-
fn test_distance_transform_1d_constant() {
507-
let f = vec![0.0, 0.0, 0.0];
508-
let dists = distance_transform_1d(&f);
509-
assert_eq!(dists, &[0.0, 0.0, 0.0]);
510-
}
511-
512-
#[test]
513-
fn test_distance_transform_1d_descending_gradient() {
514-
let f = vec![7.0, 5.0, 3.0, 1.0];
515-
let dists = distance_transform_1d(&f);
516-
assert_eq!(dists, &[6.0, 4.0, 2.0, 1.0]);
517-
}
518-
519-
#[test]
520-
fn test_distance_transform_1d_ascending_gradient() {
521-
let f = vec![1.0, 3.0, 5.0, 7.0];
522-
let dists = distance_transform_1d(&f);
523-
assert_eq!(dists, &[1.0, 2.0, 4.0, 6.0]);
524-
}
525-
526-
#[test]
527-
fn test_distance_transform_1d_with_infinities() {
528-
let f = vec![f64::INFINITY, f64::INFINITY, 5.0, f64::INFINITY];
529-
let dists = distance_transform_1d(&f);
530-
assert_eq!(dists, &[9.0, 6.0, 5.0, 6.0]);
531-
}
532-
533465
// Simple implementation of 1d distance transform which performs an
534466
// exhaustive search. Used to valid the more complicated lower-envelope
535467
// implementation against.
536-
fn distance_transform_1d_reference(f: &[f64]) -> Vec<f64> {
468+
pub fn distance_transform_1d_reference(f: &[f64]) -> Vec<f64> {
537469
let mut ret = vec![0.0; f.len()];
538470
for q in 0..f.len() {
539471
ret[q] = (0..f.len())
@@ -546,20 +478,16 @@ mod tests {
546478
ret
547479
}
548480

549-
#[cfg_attr(miri, ignore = "slow")]
550-
#[test]
551-
fn test_distance_transform_1d_matches_reference_implementation() {
552-
fn prop(f: Vec<BoundedFloat>) -> bool {
553-
let v: Vec<f64> = f.into_iter().map(|n| n.0).collect();
554-
let expected = distance_transform_1d_reference(&v);
555-
let actual = distance_transform_1d(&v);
556-
expected == actual
557-
}
558-
559-
quickcheck(prop as fn(Vec<BoundedFloat>) -> bool);
481+
pub fn distance_transform_1d(f: &Vec<f64>) -> Vec<f64> {
482+
let mut r = vec![0.0; f.len()];
483+
let mut e = LowerEnvelope::new(f.len());
484+
distance_transform_1d_mut(f, &mut r, &mut e);
485+
r
560486
}
561487

562-
fn euclidean_squared_distance_transform_reference(image: &Image<Luma<u8>>) -> Image<Luma<f64>> {
488+
pub fn euclidean_squared_distance_transform_reference(
489+
image: &Image<Luma<u8>>,
490+
) -> Image<Luma<f64>> {
563491
let (width, height) = image.dimensions();
564492

565493
let mut dists = Image::new(width, height);
@@ -586,18 +514,41 @@ mod tests {
586514
dists
587515
}
588516

589-
#[cfg_attr(miri, ignore = "slow")]
590-
#[test]
591-
fn test_euclidean_squared_distance_transform_matches_reference_implementation() {
592-
fn prop(image: GrayTestImage) -> TestResult {
593-
let expected = euclidean_squared_distance_transform_reference(&image.0);
594-
let actual = euclidean_squared_distance_transform(&image.0);
595-
match pixel_diff_summary(&actual, &expected) {
596-
None => TestResult::passed(),
597-
Some(err) => TestResult::error(err),
598-
}
517+
impl Sink for Vec<f64> {
518+
fn put(&mut self, idx: usize, value: f64) {
519+
self[idx] = value;
599520
}
600-
quickcheck(prop as fn(GrayTestImage) -> TestResult);
521+
fn len(&self) -> usize {
522+
self.len()
523+
}
524+
}
525+
526+
#[test]
527+
fn test_distance_transform_1d_constant() {
528+
let f = vec![0.0, 0.0, 0.0];
529+
let dists = distance_transform_1d(&f);
530+
assert_eq!(dists, &[0.0, 0.0, 0.0]);
531+
}
532+
533+
#[test]
534+
fn test_distance_transform_1d_descending_gradient() {
535+
let f = vec![7.0, 5.0, 3.0, 1.0];
536+
let dists = distance_transform_1d(&f);
537+
assert_eq!(dists, &[6.0, 4.0, 2.0, 1.0]);
538+
}
539+
540+
#[test]
541+
fn test_distance_transform_1d_ascending_gradient() {
542+
let f = vec![1.0, 3.0, 5.0, 7.0];
543+
let dists = distance_transform_1d(&f);
544+
assert_eq!(dists, &[1.0, 2.0, 4.0, 6.0]);
545+
}
546+
547+
#[test]
548+
fn test_distance_transform_1d_with_infinities() {
549+
let f = vec![f64::INFINITY, f64::INFINITY, 5.0, f64::INFINITY];
550+
let dists = distance_transform_1d(&f);
551+
assert_eq!(dists, &[9.0, 6.0, 5.0, 6.0]);
601552
}
602553

603554
#[test]
@@ -623,6 +574,34 @@ mod tests {
623574
}
624575
}
625576

577+
#[cfg(not(miri))]
578+
#[cfg(test)]
579+
mod proptests {
580+
use super::tests::euclidean_squared_distance_transform_reference;
581+
use super::tests::{distance_transform_1d, distance_transform_1d_reference};
582+
use super::*;
583+
use crate::proptest_utils::arbitrary_image;
584+
use proptest::prelude::*;
585+
586+
proptest! {
587+
#[test]
588+
fn test_distance_transform_1d_matches_reference_implementation(f in proptest::collection::vec(-10_000_000.0..10_000_000.0, 0..50)) {
589+
let actual = distance_transform_1d(&f);
590+
let expected = distance_transform_1d_reference(&f);
591+
592+
assert_eq!(actual, expected);
593+
}
594+
595+
#[test]
596+
fn test_euclidean_squared_distance_transform_matches_reference_implementation(image in arbitrary_image::<Luma<u8>>(0..10, 0..10)) {
597+
let expected = euclidean_squared_distance_transform_reference(&image);
598+
let actual = euclidean_squared_distance_transform(&image);
599+
600+
assert_eq!(actual, expected)
601+
}
602+
}
603+
}
604+
626605
#[cfg(not(miri))]
627606
#[cfg(test)]
628607
mod benches {

src/filter/median.rs

Lines changed: 11 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -414,13 +414,13 @@ mod benches {
414414
bench_median_filter!(bench_median_filter_s100_rx8_ry1, side: 100, x_radius: 8,y_radius: 1);
415415
}
416416

417+
#[cfg(not(miri))]
417418
#[cfg(test)]
418-
mod tests {
419+
mod proptests {
419420
use super::*;
420-
use crate::property_testing::GrayTestImage;
421-
use crate::utils::pixel_diff_summary;
421+
use crate::proptest_utils::arbitrary_image;
422422
use image::{GrayImage, Luma};
423-
use quickcheck::{quickcheck, TestResult};
423+
use proptest::prelude::*;
424424
use std::cmp::{max, min};
425425

426426
// Reference implementation of median filter - written to be as simple as possible,
@@ -470,20 +470,13 @@ mod tests {
470470
sorted[mid]
471471
}
472472

473-
#[cfg_attr(miri, ignore = "slow")]
474-
#[test]
475-
fn test_median_filter_matches_reference_implementation() {
476-
fn prop(image: GrayTestImage, x_radius: u32, y_radius: u32) -> TestResult {
477-
let x_radius = x_radius % 5;
478-
let y_radius = y_radius % 5;
479-
let expected = reference_median_filter(&image.0, x_radius, y_radius);
480-
let actual = median_filter(&image.0, x_radius, y_radius);
481-
482-
match pixel_diff_summary(&actual, &expected) {
483-
None => TestResult::passed(),
484-
Some(err) => TestResult::error(err),
485-
}
473+
proptest! {
474+
#[test]
475+
fn test_median_filter_matches_reference_implementation(image in arbitrary_image::<Luma<u8>>(0..10, 0..10), x_radius in 0_u32..5, y_radius in 0_u32..5) {
476+
let expected = reference_median_filter(&image, x_radius, y_radius);
477+
let actual = median_filter(&image, x_radius, y_radius);
478+
479+
assert_eq!(actual, expected);
486480
}
487-
quickcheck(prop as fn(GrayTestImage, u32, u32) -> TestResult);
488481
}
489482
}

src/filter/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -582,7 +582,7 @@ mod tests {
582582
// I think the interesting edge cases here are determined entirely
583583
// by the relative sizes of the kernel and the image side length, so
584584
// I'm just enumerating over small values instead of generating random
585-
// examples via quickcheck.
585+
// examples via proptesting.
586586
for height in 0..5 {
587587
for width in 0..5 {
588588
for kernel_length in 0..15 {

src/integral_image.rs

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -465,10 +465,7 @@ pub fn column_running_sum(image: &GrayImage, column: u32, buffer: &mut [u32], pa
465465
mod tests {
466466
use super::*;
467467
use crate::definitions::Image;
468-
use crate::property_testing::GrayTestImage;
469-
use crate::utils::pixel_diff_summary;
470468
use image::{GenericImage, Luma};
471-
use quickcheck::{quickcheck, TestResult};
472469

473470
#[test]
474471
fn test_integral_image_gray() {
@@ -555,7 +552,7 @@ mod tests {
555552
}
556553

557554
/// Simple implementation of integral_image to validate faster versions against.
558-
fn integral_image_ref<I>(image: &I) -> Image<Luma<u32>>
555+
pub fn integral_image_ref<I>(image: &I) -> Image<Luma<u32>>
559556
where
560557
I: GenericImage<Pixel = Luma<u8>>,
561558
{
@@ -579,19 +576,25 @@ mod tests {
579576

580577
out
581578
}
579+
}
582580

583-
#[cfg_attr(miri, ignore = "slow")]
584-
#[test]
585-
fn test_integral_image_matches_reference_implementation() {
586-
fn prop(image: GrayTestImage) -> TestResult {
587-
let expected = integral_image_ref(&image.0);
588-
let actual = integral_image(&image.0);
589-
match pixel_diff_summary(&actual, &expected) {
590-
None => TestResult::passed(),
591-
Some(err) => TestResult::error(err),
592-
}
581+
#[cfg(not(miri))]
582+
#[cfg(test)]
583+
mod proptests {
584+
use super::tests::integral_image_ref;
585+
use super::*;
586+
use crate::proptest_utils::arbitrary_image;
587+
use image::Luma;
588+
use proptest::prelude::*;
589+
590+
proptest! {
591+
#[test]
592+
fn test_integral_image_matches_reference_implementation(image in arbitrary_image::<Luma<u8>>(0..10, 0..10)) {
593+
let expected = integral_image_ref(&image);
594+
let actual = integral_image(&image);
595+
596+
assert_eq!(expected, actual);
593597
}
594-
quickcheck(prop as fn(GrayTestImage) -> TestResult);
595598
}
596599
}
597600

src/lib.rs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,6 @@ pub mod morphology;
4242
pub mod noise;
4343
pub mod pixelops;
4444
pub mod point;
45-
#[cfg(any(feature = "property-testing", test))]
46-
pub mod property_testing;
4745
pub mod rect;
4846
pub mod region_labelling;
4947
pub mod seam_carving;

0 commit comments

Comments
 (0)