#fork #section #nested #test #subtest

macro crossroads

A proc-macro that turns one function into many along a user-defined set of fork points!

2 releases

0.1.1 Dec 21, 2022
0.1.0 Dec 21, 2022

#527 in Testing

MIT license

18KB
154 lines

crossroads library

This library consists of a single proc macro with a simple purpose: Turn one function into potentially many!

To motivate this, assume you have a couple of test cases like these:

use std::collections::HashMap;

#[test]
fn empty_be_default() {
    let map: HashMap<String, usize> = Default::default();
    
    assert!(map.is_empty());
}

#[test]
fn empty_after_clear() {
    let mut map: HashMap<String, usize> = Default::default();

    map.insert("test".to_string(), 1);
    map.clear();

    assert!(map.is_empty());
}

#[test]
fn empty_after_remove() {
    let mut map: HashMap<String, usize> = Default::default();

    map.insert("test".to_string(), 1);
    map.remove("test");

    assert!(map.is_empty());
}

This crate allows you to write the following instead:

use std::collections::HashMap;
use crossroads::crossroads;

#[crossroads]
#[test]
fn empty() {
    let mut map: HashMap<String, usize> = Default::default();

    match fork!() {
        by_default => {}
        after_add => {
            map.insert("Key".to_owned(), 1337);
            match fork!() {
                and_remove => map.remove("Key"),
                and_clear => map.clear(),
            };
        }
    }

    assert!(map.is_empty());
}

The macro computes every possible path through the fork!()s and creates a version of the function for it. These can then be picked up by the test system (as attributes below the #[crossroads] attribute are cloned as well). In this case, you will get output like this (run cargo test --examples to reproduce:

running 3 tests
test empty_by_default ... ok
test empty_after_add_and_clear ... ok
test empty_after_add_and_remove ... ok

The idea is inspired by @Nested tests in JUnit 5 , sections in Catch2 and subtests in doctest .

Questions

Answers to common questions:

  1. Why did you decide to use the standard match syntax instead of introducing a custom one, for example fork! { a => { .. }, b => { .. }}?

It would indeed be possible to offer a completely custom syntax with the proc macro. However, I decided to just latch onto the match structure as it makes code-formatting tools, in particular rustfmt work a lot more smoothly.

Contributions

I welcome any suggestions/feature requests/changes/bug fixes. Feel free to open a PR if you already have concrete changes at hand. Alternatively, open an issue and we can discuss possible solutions/viability.

Please note that any contributions that you make to this project are assumed to be licensed under the same license(s) as the remainder of project. Make sure that you are legally allowed to grant such licenses to your work.

General requirements:

  • CI checks must all pass on your PR.
  • After review/approval, please rebase to the latest version of the master branch.

License

The library is licensed under MIT license, see LICENSE for details.

Dependencies

~1.5MB
~34K SLoC