section_testing-0.0.4/.gitignore010064400007650000024000000000101335401020400151020ustar0000000000000000target/ section_testing-0.0.4/Cargo.toml.orig010064400007650000024000000003411335401302400160130ustar0000000000000000[package] name = "section_testing" version = "0.0.4" description = "A library for section-style testing" license = "MIT" authors = ["Evan Wallace"] readme = "README.md" repository = "https://github.com/evanw/section_testing" section_testing-0.0.4/Cargo.toml0000644000000013550000000000000122770ustar00# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g. crates.io) dependencies # # If you believe there's an error in this file please file an # issue against the rust-lang/cargo repository. If you're # editing this file be aware that the upstream Cargo.toml # will likely look very different (and much more reasonable) [package] name = "section_testing" version = "0.0.4" authors = ["Evan Wallace"] description = "A library for section-style testing" readme = "README.md" license = "MIT" repository = "https://github.com/evanw/section_testing" section_testing-0.0.4/LICENSE.md010064400007650000024000000020341335401200600145300ustar0000000000000000Copyright 2018 Evan Wallace 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. section_testing-0.0.4/README.md010064400007650000024000000054211335401316500144150ustar0000000000000000This is a small library that enables section-style testing in Rust. Section-style testing makes writing many similar test cases easy, natural, and concise. Each top-level section test is run repeatedly, once for every unique section inside the test. This is more expressive and natural than fixtures because it lets you use local variables from parent scopes inside a section and because you can nest sections to an arbitrary depth. # Getting Started This library is published at https://crates.io/crates/section_testing. Add the following dependency to your `Cargo.toml`: ``` [dependencies] section_testing = "0.0.4" ``` Read the example below to learn how to use it. # Example Here's an example: ```rust #[macro_use] extern crate section_testing; enable_sections! { #[test] fn example_test() { let mut v: Vec = vec![]; fn check_123(v: &mut Vec) { assert_eq!(*v, vec![1, 2, 3]); if section!("reverse") { v.reverse(); assert_eq!(*v, vec![3, 2, 1]); } if section!("pop+remove+insert+push") { let three = v.pop().unwrap(); let one = v.remove(0); v.insert(0, three); v.push(one); assert_eq!(*v, vec![3, 2, 1]); } } if section!("push") { v.push(1); v.push(2); v.push(3); check_123(&mut v); } if section!("insert") { v.insert(0, 3); v.insert(0, 1); v.insert(1, 2); check_123(&mut v); } } } ``` The `enable_sections!` macro modifies the test functions inside of it so that they run repeatedly until all sections have been visited. The `section!` macro returns a `bool` for whether or not that section should be run this iteration. This example test will check the following combinations: ``` push push, reverse push, pop+remove+insert+push insert insert, reverse insert, pop+remove+insert+push ``` When a test fails, the enclosing sections will be printed to stderr. Here's what happens if we comment out `v.push(one);` in the example above: ``` running 1 test thread 'example_test' panicked at 'assertion failed: `(left == right)` left: `[3, 2]`, right: `[3, 2, 1]`', src/main.rs:30:9 note: Run with `RUST_BACKTRACE=1` for a backtrace. ---- the failure was inside these sections ---- 0) "push" at src/main.rs:34 1) "pop+remove+insert+push" at src/main.rs:25 test example_test ... FAILED ``` Note that like all tests in Rust, a section-style test will stop on the first failure. This means you will only be able to see the first combination that failed instead of being able to see all failed combinations. The above example would have also failed for the combination `insert, pop+remove+insert+push` if the other combination hadn't failed first. This is because Rust's built-in test runner has no API for adding new tests at runtime. section_testing-0.0.4/src/lib.rs010064400007650000024000000160161335400747700150540ustar0000000000000000//! This is a small library that enables section-style testing in Rust. //! Section-style testing makes writing many similar test cases easy, natural, //! and concise. //! //! Each top-level section test is run repeatedly, once for every unique //! section inside the test. This is more expressive and natural than fixtures //! because it lets you use local variables from parent scopes inside a section //! and because you can nest sections to an arbitrary depth. //! //! Here's an example: //! //! ```rust,ignore //! #[macro_use] //! extern crate section_testing; //! //! enable_sections! { //! #[test] //! fn example_test() { //! let mut v: Vec = vec![]; //! //! fn check_123(v: &mut Vec) { //! assert_eq!(*v, vec![1, 2, 3]); //! //! if section!("reverse") { //! v.reverse(); //! assert_eq!(*v, vec![3, 2, 1]); //! } //! //! if section!("pop+remove+insert+push") { //! let three = v.pop().unwrap(); //! let one = v.remove(0); //! v.insert(0, three); //! v.push(one); //! assert_eq!(*v, vec![3, 2, 1]); //! } //! } //! //! if section!("push") { //! v.push(1); //! v.push(2); //! v.push(3); //! check_123(&mut v); //! } //! //! if section!("insert") { //! v.insert(0, 3); //! v.insert(0, 1); //! v.insert(1, 2); //! check_123(&mut v); //! } //! } //! } //! ``` //! //! The `enable_sections!` macro modifies the test functions inside of it so //! that they run repeatedly until all sections have been visited. The //! `section!` macro returns a `bool` for whether or not that section should be //! run this iteration. This example test will check the following combinations: //! //! ```text //! push //! push, reverse //! push, pop+remove+insert+push //! insert //! insert, reverse //! insert, pop+remove+insert+push //! ``` //! //! When a test fails, the enclosing sections will be printed to stderr. Here's //! what happens if we comment out `v.push(one);` in the example above: //! //! ```text //! running 1 test //! thread 'example_test' panicked at 'assertion failed: `(left == right)` //! left: `[3, 2]`, //! right: `[3, 2, 1]`', src/main.rs:30:9 //! note: Run with `RUST_BACKTRACE=1` for a backtrace. //! ---- the failure was inside these sections ---- //! 0) "push" at src/main.rs:34 //! 1) "pop+remove+insert+push" at src/main.rs:25 //! test example_test ... FAILED //! ``` //! //! Note that like all tests in Rust, a section-style test will stop on the //! first failure. This means you will only be able to see the first combination //! that failed instead of being able to see all failed combinations. The above //! example would have also failed for the combination `insert, //! pop+remove+insert+push` if the other combination hadn't failed first. This //! is because Rust's built-in test runner has no API for adding new tests at //! runtime. use std::mem::swap; use std::fmt::Write; use std::cell::RefCell; use std::collections::{HashMap, VecDeque}; thread_local! { static CURRENT_RUNNER: RefCell = RefCell::new(Runner::new()); } #[derive(PartialEq, Eq, Hash, Clone, Copy)] struct Section { name: &'static str, file: &'static str, line: u32, } #[derive(Clone, Copy)] struct Entry { should_enter: bool, index: usize, } struct Runner { is_running: bool, queue: VecDeque>, current: HashMap, new: Vec
, } impl Runner { fn new() -> Runner { Runner { is_running: false, queue: vec![HashMap::new()].into(), current: HashMap::new(), new: vec![], } } } pub struct DropHandler { pub is_top_level: bool, pub was_success: bool, } impl Drop for DropHandler { fn drop(&mut self) { if !self.is_top_level { return; } CURRENT_RUNNER.with(|r| { r.borrow_mut().is_running = false; // Did the test complete successfully? if self.was_success { let mut r = r.borrow_mut(); let mut new = vec![]; swap(&mut r.new, &mut new); // If so, add newly-discovered sections to the queue for section in &new { let mut path = r.current.clone(); let mut count = r.current.values().filter(|x| x.should_enter).count(); for s in &new { path.insert(*s, Entry { should_enter: s == section, index: count, }); } r.queue.push_back(path); } } // Is the test in the middle of unwinding due to a panic? else { let mut current: Vec<_> = r.borrow().current.iter() .map(|(k, v)| (*k, *v)) .filter(|(_, v)| v.should_enter) .collect(); current.sort_unstable_by(|a, b| a.1.index.cmp(&b.1.index)); // Write out the failure as a single buffer to avoid it interleaving with other output if !current.is_empty() { let mut buffer = "---- the failure was inside these sections ----\n".to_owned(); for (i, (section, _)) in current.iter().enumerate() { write!(&mut buffer, "{: >3}) {:?} at {}:{}\n", i, section.name, section.file, section.line).unwrap(); } eprint!("{}", buffer); } } }); } } pub fn enable_sections_start() -> bool { CURRENT_RUNNER.with(|r| { if r.borrow().is_running { false } else { r.replace(Runner::new()); true } }) } pub fn enable_sections_step() -> bool { CURRENT_RUNNER.with(|r| { let mut r = r.borrow_mut(); if let Some(current) = r.queue.pop_front() { r.current = current; r.new.clear(); r.is_running = true; true } else { false } }) } pub fn enter_section(name: &'static str, file: &'static str, line: u32) -> bool { CURRENT_RUNNER.with(|r| { let section = Section {name, file, line}; let should_enter = r.borrow().current.get(§ion).map(|x| x.should_enter); should_enter.unwrap_or_else(|| { r.borrow_mut().new.push(section); false }) }) } pub fn is_running() -> bool { CURRENT_RUNNER.with(|r| r.borrow().is_running) } #[macro_export] macro_rules! enable_sections { ( $( $(#[$($attrs:tt)*])* fn $name:ident() { $($arg:tt)* } )* ) => { $( $(#[$($attrs)*])* fn $name() { let is_top_level = $crate::enable_sections_start(); loop { // Stop this run when the queue is empty if is_top_level && !$crate::enable_sections_step() { break; } // Run the function body let mut scope = $crate::DropHandler {is_top_level, was_success: false}; $($arg)* scope.was_success = true; // Only run the function body once if we're not top-level if !is_top_level { break; } } } )* } } #[macro_export] macro_rules! section { ($name:expr) => {{ assert!($crate::is_running(), "\"section!(...)\" must be called from inside \"enable_sections! { ... }\""); $crate::enter_section($name, file!(), line!()) }} }