srctrait-common-testing 4.0.0

Structured testing with setup, teardown, and standardized fixture and temp directories
Documentation
SrcTrait Common Testing
===============================================================================
[![Crate Badge]][Crate] [![Docs Badge]][Docs] [![License Badge]][License]

*Structured testing with setup, teardown, and standardized fixture and temp directories*

## Features
- Structure tests into inheritable heirarchies: Module -> Test
- Bundle common assets outside of heirarchies using Group
- Setup and teardown callbacks for each Module, Test, and Group 
- Module and Group setup occurs before Test
- Categorize based on use-case: Unit and Integration
- Standardized paths for fixture and temp directories 
- Miscellaneous helper functions for common tasks

## Restrictions
- One Module per Rust test module
- One Test per Rust test function
- Module and Group are static to a Rust module
- Test is instantiated non-static inside of the Rust test function
- Unit tests live in `src`
- Integration tests live in `tests` or `example`
- Fixture asset files live in `testing`

## Standard paths
> *use-case / module_path / function_name*

## Example
```rust
// Just something to test against
pub fn fibonacci(n: u8) -> Option<u128> {
    if n == 0 {
        return Some(0);
    } else if n == 1 {
        return Some(1);
    } else if n > FIBONACCI_MAX_N_U128 { // overflow u128
        return None;
    }

    let mut sum: u128 = 0;
    let mut last: u128 = 0;
    let mut curr: u128 = 1;
    for _i in 1..n {
        if let Some(r) = last.checked_add(curr) {
            sum = r;
        } else {
            return None;
        }

        last = curr;
        curr = sum;
    }

    Some(sum)
}

const FIBONACCI_MAX_N_U128: u8 = 185;

// Testing namepaths will strip '::tests' out.
#[cfg(test)]
mod tests {
    use std::{fs, sync::LazyLock};
    use super::*;
    use srctrait_common_testing::prelude::*;

    // Module setup runs before any tests that use the module.
    // Teardown runs on process exit.
    //
    // Fixture dirs are enforced at runtime and panic if they don't exist.
    //
    // This module's namepath().full_path() will be:
    //     srctrait-common-testing/integration/example-fibonacci
    //
    // This module's namepath().path() will be:
    //     integration/example-fibonacci
    //
    // fixture dir: $CRATE/testing/fixtures/integration/example-fibonacci
    // temp dir: $TMP/srctrait-common-testing-example-fibonacci.XXXXXXXX
    static TESTING: testing::Module = testing::module!(Integration, {
            .using_fixture_dir()
            .using_temp_dir()
            .teardown_static(teardown)
            .setup(|module| {
                let fibonacci_u128_csv = (0..FIBONACCI_MAX_N_U128)
                    .map(|n| fibonacci(n).unwrap().to_string())
                    .collect::<Vec<_>>()
                    .join(",");

                let fibonacci_csv_tmp_file = module.temp_dir()
                    .join(&*FIBONACCI_U128_CSV_FILENAME);

                fs::write(&fibonacci_csv_tmp_file, fibonacci_u128_csv).unwrap();
            })
    });

    static FIBONACCI_U128_CSV_FILENAME: LazyLock<String> = LazyLock::new(||
        "fibonacci-u128.csv".to_string());

    // Anything const or static can be used with static teardown functions,
    // including things like LazyLock.
    //
    // Module's internal teardown will delete its temp directory before
    // running its custom teardown.
    extern "C" fn teardown() {
        println!("Farewell, Fibonacci. Don't worry, {} has already been deleted.",
            *FIBONACCI_U128_CSV_FILENAME);
    }

    // Groups are standalone and work similar to Modules, but without children.
    // They have their own tmp and fixture directories, setup, and teardown.
    //
    // This group's namepath().full_path() will be:
    //     srctrait-common-testing/integration/example-fibonacci/100
    // This group's namepath().path() will be:
    //     integration/example-fibonacci/100
    static GROUP_100: testing::Group = testing::group!("example-fibonacci/100", Integration, {
        .using_fixture_dir()
    });

    // The #[tested] helper is merely the equivalent of #[test] and #[named]
    #[tested]
    fn test_u128() {
        // test!() assumes a module named `TESTING` is in scope and links to it.
        //
        // The "inherit_" methods use the same dir as their parent module.
        // The "using_" methods will append a subdir named for the test function.
        //
        // Tests block on their module's setup. The tmp file needed here will exist.
        //
        // fixture dir: $CRATE/testing/fixtures/integration/example-fibonacci/test-u128
        // tmp dir: $TMP/srctrait-common-testing-example-fibonacci.XXXXXXXX
        let test = testing::test!({
            .using_fixture_dir()
            .inherit_temp_dir()
        });

        let fibonacci_fixture_csv_file = test.fixture_dir().join(&*FIBONACCI_U128_CSV_FILENAME);
        let expected_fibonacci_u128 = fs::read_to_string(fibonacci_fixture_csv_file).unwrap()
            .split(",")
            .map(|d| d.trim().parse().unwrap())
            .collect::<Vec<u128>>();

        let fibonacci_tmp_csv_file = test.temp_dir().join(&*FIBONACCI_U128_CSV_FILENAME);
        let actual_fibonacci_u128 = fs::read_to_string(fibonacci_tmp_csv_file).unwrap()
            .split(",")
            .map(|i| i.parse().unwrap())
            .collect::<Vec<u128>>();

        assert_eq!(expected_fibonacci_u128, actual_fibonacci_u128);
    }

    // Demonstration using a Group
    #[tested]
    fn test_100() {
        // This empty Test will block on its Module's setup.
        let _test = testing::test!();

        // Groups will block on their setup.
        let fib100_file = GROUP_100.fixture_dir().join("fib-100.txt");

        let fib100: u128 = fs::read_to_string(fib100_file)
            .map(|d| d.trim().parse().unwrap())
            .unwrap();

        assert_eq!(fib100, fibonacci(100).unwrap());
    }
}
```

## Documentation
Refer to [docs.rs/srctrait-common-testing](https://docs.rs/srctrait-common-testing/latest/srctrait_common_testing)

Repository
--------------------------------------------------------------------------------
Contributors, please review [SRCTRAIT.md](./SRCTRAIT.md).  

Found a bug? Search for an existing issue on GitHub.  
If an issue exists, chime in to add weight to it.  
If not, create one and let us know. 


License (AGPL3)
--------------------------------------------------------------------------------
SrcTrait Common Testing: Structured testing in sequence with fixture and temp directories  
Developed by [SourceTrait](https://sourcetrait.com)  
Copyright (C) 2025 [Asmov LLC](https://asmov.software)  

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU Affero General Public License for more details.

You should have received a [copy](./LICENSE-AGPL-3.txt) of the
GNU Affero General Public License along with this program.
If not, see https://www.gnu.org/licenses/.


Third-Party Licenses
-------------------------------------------------------------------------------
### crate: [function_name]https://crates.io/crates/function_name

>Our library publically exports the **named** macro from [Daniel Henry-Mantilla]https://github.com/danielhenrymantilla's crate: [function_name]https://github.com/danielhenrymantilla/rust-function_name. It is available for use from our crate as `srctrait_common_testing::named`.

**License (MIT):**  
[Copyright (c) 2019 Daniel Henry-Mantilla](./docs/licenses/danielhenrymantilla/function_name/LICENSE.txt)


[Crate]: https://crates.io/crates/srctrait-common-testing
[Crate Badge]: https://img.shields.io/crates/v/srctrait-common-testing.svg
[Docs]: https://docs.rs/srctrait-common-testing
[Docs Badge]: https://img.shields.io/badge/docs-blue
[License]: #License-AGPL3
[License Badge]: https://img.shields.io/badge/license-AGPL3-blue.svg