Permalink
Browse files

Initial commit of the 'smallcheck' library

This is the first commit of this project. It is not functional.
There's a note left in `src/serial.rs` as to why. Getting the Serial
trait correct will be the tricky bit here, everything else is
friendly window-dressing, at least in so far as I am aware from this
place of wild-eyed naivety.

Signed-off-by: Brian L. Troutwine <brian@troutwine.us>
  • Loading branch information...
blt committed Jun 4, 2017
0 parents commit 6b758b33de5303890e78116177281c7d4ec9e57b
Showing with 334 additions and 0 deletions.
  1. +3 −0 .gitignore
  2. +10 −0 Cargo.toml
  3. +21 −0 LICENSE.md
  4. +18 −0 README.md
  5. +27 −0 src/lib.rs
  6. +78 −0 src/serial.rs
  7. +177 −0 src/tester.rs
@@ -0,0 +1,3 @@
/target/
**/*.rs.bk
Cargo.lock
@@ -0,0 +1,10 @@
[package]
name = "smallcheck"
version = "0.1.0"
description = "An implementation of 'SmallCheck and Lazy SmallCheck' and 'Advances in Lazy SmallCheck'"
authors = ["Brian L. Troutwine <brian@troutwine.us>"]
keywords = ["testing", "exhaustive"]
categories = ["development-tools::testing"]
license = "MIT"
[dependencies]
@@ -0,0 +1,21 @@
The MIT License
Copyright (c) 2017 Brian L. Troutwine
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
@@ -0,0 +1,18 @@
# smallcheck - an exhaustive checker
smallcheck is an exhaustive checker.
In the vein of [quickcheck](https://github.com/BurntSushi/quickcheck)
`smallcheck` is a testing tool which asks that you specify properites of the
system under test and automatically generates inputs for testing. Where
`quickcheck` uses random sampling over the domain of inputs `smallcheck`
exhausts the domain, to some 'depth', going from smallest to largest values.
This implementation is based on:
* Runciman, C., Naylor, M., & Lindblad, F. (2008). Smallcheck and lazy smallcheck: automatic exhaustive testing for small values. Acm Sigplan Notices.
* Reich, J. S., Naylor, M., & Runciman, C. (2012). Advances in Lazy SmallCheck. In Implementation and Application of Functional Languages (Vol. 8241, pp. 53–70). Berlin, Heidelberg: Springer, Berlin, Heidelberg. http://doi.org/10.1007/978-3-642-41582-1_4
- - -
I listened to so much Brian Eno when writing this library. So. Much.
@@ -0,0 +1,27 @@
//! smallcheck is an exhaustive checker.
//!
//! In the vein of [quickcheck](https://github.com/BurntSushi/quickcheck)
//! `smallcheck` is a testing tool which asks that you specify properites of the
//! system under test and automatically generates inputs for testing. Where
//! `quickcheck` uses random sampling over the domain of inputs `smallcheck`
//! exhausts the domain, to some 'depth', going from smallest to largest values.
//!
//! This implementation is based on:
//!
//! * Runciman, C., Naylor, M., & Lindblad, F. (2008). Smallcheck and lazy smallcheck: automatic exhaustive testing for small values. Acm Sigplan Notices.
//! * Reich, J. S., Naylor, M., & Runciman, C. (2012). Advances in Lazy SmallCheck. In Implementation and Application of Functional Languages (Vol. 8241, pp. 53–70). Berlin, Heidelberg: Springer, Berlin, Heidelberg. http://doi.org/10.1007/978-3-642-41582-1_4
//!
//! Much inspiration is taken from the Rust version of QuickCheck with regard to
//! API and project structure.
#![deny(trivial_numeric_casts,
missing_docs,
unstable_features,
unused_import_braces,
)]
mod tester;
mod serial;
pub use tester::{SmallCheck, Testable, TestResult};
pub use serial::Serial;
@@ -0,0 +1,78 @@
/// `Serial` describes an ordered enumeration over a type. The implementor gets
/// to decide what 'small' means.
pub trait Serial: Clone + Send + 'static {
fn new() -> Self;
fn next<S: Serial>(s: &mut S) -> Option<Self>;
}
impl Serial for () {
fn new() -> () {
()
}
fn next<S: Serial>(_: &mut S) -> Option<()> { None }
}
macro_rules! impl_serial_for_tuple {
(($var_a:ident, $type_a:ident) $(, ($var_n:ident, $type_n:ident))*) => (
impl<$type_a: Serial, $($type_n: Serial),*> Serial
for ($type_a, $($type_n),*) {
fn new() -> ($type_a, $(type_n),*) {
(
$type_a::new(),
$({
$type_n::new()
},
)*
)
}
// Well, crap. I think to implement `next` for an arbitrary
// structure I'm going to have to look into the lazy
// approach. The problem here being that, while I can fiddle
// with simple types directly this is a cheat. `Serial`
// really has some state or, absent state, must _always_ be
// able to be interpreted into the 'next' value.
}
);
}
impl_serial_for_tuple!((a, A));
impl_serial_for_tuple!((a, A), (b, B));
impl_serial_for_tuple!((a, A), (b, B), (c, C));
impl_serial_for_tuple!((a, A), (b, B), (c, C), (d, D));
impl_serial_for_tuple!((a, A), (b, B), (c, C), (d, D), (e, E));
impl_serial_for_tuple!((a, A), (b, B), (c, C), (d, D), (e, E), (f, F));
impl_serial_for_tuple!((a, A), (b, B), (c, C), (d, D), (e, E), (f, F),
(g, G));
impl_serial_for_tuple!((a, A), (b, B), (c, C), (d, D), (e, E), (f, F),
(g, G), (h, H));
impl_serial_for_tuple!((a, A), (b, B), (c, C), (d, D), (e, E), (f, F),
(g, G), (h, H), (i, I));
impl_serial_for_tuple!((a, A), (b, B), (c, C), (d, D), (e, E), (f, F),
(g, G), (h, H), (i, I), (j, J));
impl_serial_for_tuple!((a, A), (b, B), (c, C), (d, D), (e, E), (f, F),
(g, G), (h, H), (i, I), (j, J), (k, K));
impl_serial_for_tuple!((a, A), (b, B), (c, C), (d, D), (e, E), (f, F),
(g, G), (h, H), (i, I), (j, J), (k, K), (l, L));
macro_rules! unsigned_serial {
($($ty:ty),*) => {
$(
impl Serial for $ty {
fn new() -> $ty {
0
}
fn next<S: Serial>(s: &mut S) -> Option<$ty> {
*s.checked_add(1)
}
}
)*
}
}
unsigned_arbitrary! {
usize, u8, u16, u32, u64
}
@@ -0,0 +1,177 @@
use Serial;
use std::fmt;
use std::panic;
use tester::Status::{Pass, Fail};
/// The primary `smallcheck` type for setting search depth and running tests.
pub struct SmallCheck<S> where S: Serial {
depth: usize,
serial: S,
}
impl<S> SmallCheck<S> where S: Serial {
pub fn new() -> SmallCheck<S> {
SmallCheck {
depth: 100,
serial: S::new(),
}
}
pub fn test<A>(&mut self, f: A) -> Result<usize, TestResult> where A: Testable {
unimplemented!()
}
/// Test a property and call `panic!` on failure.
///
/// Because of the way SmallCheck works the `panic!` will contain the
/// 'smallest' failing case for your input.
///
/// This method plugs into Rust's unit testing infrastructure, unlike
/// `test`.
pub fn check<A>(&mut self, f: A) where A: Testable {
match self.test(f) {
Ok(total_tests) => println!("(Passed {} tests.)", total_tests),
Err(result) => panic!(result.failed_msg()),
}
}
}
/// `Testable` describes types (e.g., a function) whose values can be
/// tested.
///
/// tbh I don't fully know what this means for `smallcheck` yet. I have vague
/// ideas.
pub trait Testable: Send + 'static {
fn result<S: Serial>(&self, &mut S) -> TestResult;
}
impl Testable for bool {
fn result<S: Serial>(&self, _: &mut S) -> TestResult {
TestResult::from_bool(*self)
}
}
impl Testable for () {
fn result<S: Serial>(&self, _: &mut S) -> TestResult {
TestResult::passed()
}
}
impl Testable for TestResult {
fn result<S: Serial>(&self, _: &mut S) -> TestResult { self.clone() }
}
impl<A, E> Testable for Result<A, E>
where A: Testable, E: fmt::Debug + Send + 'static {
fn result<S: Serial>(&self, s: &mut S) -> TestResult {
match *self {
Ok(ref r) => r.result(s),
Err(ref err) => TestResult::error(format!("{:?}", err)),
}
}
}
macro_rules! testable_fn {
($($name: ident),*) => {
impl<T: Testable, $($name: Serial + fmt::Debug),*> Testable for fn($($name),*) -> T {
fn result<S: Serial>(&self, s: &mut S) -> TestResult {
let a: Option<($($name,)*)> = Serial::next(s);
if let Some(a) = a {
let self_ = *self;
let ( $($name,)* ) = a.clone();
let mut r = safe(move || {self_($($name),*)}).result(s);
let ( $($name,)* ) = a.clone();
r.arguments = vec![$(format!("{:?}", $name),)*];
r
} else {
TestResult::passed()
}
}
}
}
}
testable_fn!();
testable_fn!(A);
testable_fn!(A, B);
testable_fn!(A, B, C);
testable_fn!(A, B, C, D);
testable_fn!(A, B, C, D, E);
testable_fn!(A, B, C, D, E, F);
testable_fn!(A, B, C, D, E, F, G);
testable_fn!(A, B, C, D, E, F, G, H);
testable_fn!(A, B, C, D, E, F, G, H, I);
testable_fn!(A, B, C, D, E, F, G, H, I, J);
testable_fn!(A, B, C, D, E, F, G, H, I, J, K);
testable_fn!(A, B, C, D, E, F, G, H, I, J, K, L);
fn safe<T, F>(fun: F) -> Result<T, String>
where F: FnOnce() -> T, F: Send + 'static, T: Send + 'static {
panic::catch_unwind(panic::AssertUnwindSafe(fun)).map_err(|any_err| {
// Extract common types of panic payload:
// panic and assert produce &str or String
if let Some(&s) = any_err.downcast_ref::<&str>() {
s.to_owned()
} else if let Some(s) = any_err.downcast_ref::<String>() {
s.to_owned()
} else {
"UNABLE TO SHOW RESULT OF PANIC.".to_owned()
}
})
}
/// Describes the status of a single instance of a test.
///
/// All testable things must be capable of producing a `TestResult`.
#[derive(Clone, Debug)]
pub struct TestResult {
status: Status,
arguments: Vec<String>,
err: Option<String>,
}
/// Whether a test has passed, failed or been discarded.
#[derive(Clone, Debug)]
enum Status { Pass, Fail }
impl TestResult {
/// Produces a test result that indicates the current test has passed.
pub fn passed() -> TestResult { TestResult::from_bool(true) }
/// Produces a test result that indicates the current test has failed.
pub fn failed() -> TestResult { TestResult::from_bool(false) }
/// Produces a test result that indicates failure from a runtime error.
pub fn error<S: Into<String>>(msg: S) -> TestResult {
let mut r = TestResult::from_bool(false);
r.err = Some(msg.into());
r
}
/// Converts a `bool` to a `TestResult`. A `true` value indicates that
/// the test has passed and a `false` value indicates that the test
/// has failed.
pub fn from_bool(b: bool) -> TestResult {
TestResult {
status: if b { Pass } else { Fail },
arguments: vec![],
err: None,
}
}
fn failed_msg(&self) -> String {
match self.err {
None => {
format!("[quickcheck] TEST FAILED. Arguments: ({})",
self.arguments.connect(", "))
}
Some(ref err) => {
format!("[quickcheck] TEST FAILED (runtime error). \
Arguments: ({})\nError: {}",
self.arguments.connect(", "), err)
}
}
}
}

0 comments on commit 6b758b3

Please sign in to comment.