Skip to content

Add global setup/teardown hooks for test suites #256

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -50,5 +50,9 @@ harness = false
name = "integration"
harness = true

[[test]]
name = "setup_teardown"
harness = false

[features]
include-dir = ["dep:include_dir"]
26 changes: 26 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,32 @@ Because the files don’t exist on disk, the test functions must accept their
contents as either a `String` or a `Vec<u8>`. If the argument is not
provided, the harness will panic at runtime.

### Optional Setup and Teardown

The harness macro supports optional setup and teardown functions that run before and after all tests:

```rust
fn setup() -> datatest_stable::Result<()> {
// Setup code here - runs before all tests.
Ok(())
}

fn teardown(exit_code: std::process::ExitCode) -> datatest_stable::Result<()> {
// Teardown code here - runs after all tests.
Ok(())
}

datatest_stable::harness! {
setup = setup,
teardown = teardown,
{ test = my_test, root = "path/to/fixtures" },
}
```

The setup function can be used to prepare test environment.
The teardown function receives the exit code from test execution and can be used for cleanup.
Both functions are optional and can be used independently.

### Conditionally embedding directories

It is also possible to conditionally include directories at compile time via
Expand Down
28 changes: 26 additions & 2 deletions src/macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,11 @@
/// a target](https://doc.rust-lang.org/cargo/reference/manifest.html#configuring-a-target)).
#[macro_export]
macro_rules! harness {
( $( { $($args:tt)* } ),+ $(,)* ) => {
(
$(setup = $setup:path,)?
$(teardown = $teardown:path,)?
$( { $($args:tt)* } ),+ $(,)*
) => {
fn main() -> ::std::process::ExitCode {
let mut requirements = Vec::new();
use $crate::data_source_kinds::*;
Expand All @@ -17,7 +21,27 @@ macro_rules! harness {
$crate::harness_collect!(@gather_test requirements, { $($args)*, } => { });
)+

$crate::runner(&requirements)
if !requirements.is_empty() {
$(
if let Err(e) = $setup() {
eprintln!("Setup failed: {}", e);
return ::std::process::ExitCode::FAILURE;
}
)?

let exit_code = $crate::runner(&requirements);

$(
if let Err(e) = $teardown(exit_code) {
eprintln!("Teardown failed: {}", e);
return ::std::process::ExitCode::FAILURE;
}
)?

exit_code
} else {
::std::process::ExitCode::SUCCESS
}
}
};
( $( $name:path, $root:expr, $pattern:expr ),+ $(,)* ) => {
Expand Down
65 changes: 65 additions & 0 deletions tests/setup_teardown.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// Copyright (c) The datatest-stable Contributors
// SPDX-License-Identifier: MIT OR Apache-2.0

use camino::Utf8Path;
use datatest_stable::Result;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::path::Path;

static SETUP_COUNTER: AtomicUsize = AtomicUsize::new(0);
static TEARDOWN_COUNTER: AtomicUsize = AtomicUsize::new(0);
static TEST_ARTIFACT_COUNTER: AtomicUsize = AtomicUsize::new(0);
static TEST_ARTIFACT_UTF8_COUNTER: AtomicUsize = AtomicUsize::new(0);

fn test_setup() -> Result<()> {
SETUP_COUNTER.fetch_add(1, Ordering::SeqCst);
println!("Setup called");
Ok(())
}

fn test_teardown(exit_code: std::process::ExitCode) -> Result<()> {
TEARDOWN_COUNTER.fetch_add(1, Ordering::SeqCst);
if exit_code == std::process::ExitCode::SUCCESS {
println!("Teardown called after success");
} else {
println!("Teardown called after failure");
}
Ok(())
}

fn test_artifact(_path: &Path) -> Result<()> {
TEST_ARTIFACT_COUNTER.fetch_add(1, Ordering::SeqCst);
Ok(())
}

fn test_artifact_utf8(_path: &Utf8Path) -> Result<()> {
TEST_ARTIFACT_UTF8_COUNTER.fetch_add(1, Ordering::SeqCst);
Ok(())
}

datatest_stable::harness! {
setup = test_setup,
teardown = test_teardown,
{
test = test_artifact,
root = "tests/files",
},
{
test = test_artifact_utf8,
root = "tests/files",
}
}

#[test]
fn verify_counters() {
// Setup should be called exactly once.
assert_eq!(SETUP_COUNTER.load(Ordering::SeqCst), 1);

// Teardown should be called exactly once.
assert_eq!(TEARDOWN_COUNTER.load(Ordering::SeqCst), 1);

// Each test function should be called once per file in tests/files.
let file_count = fs::read_dir("tests/files").unwrap().count();
assert_eq!(TEST_ARTIFACT_COUNTER.load(Ordering::SeqCst), file_count);
assert_eq!(TEST_ARTIFACT_UTF8_COUNTER.load(Ordering::SeqCst), file_count);
}
Loading