diff --git a/crates/saft-macro/src/discover_tests.rs b/crates/saft-macro/src/discover_tests.rs index a2d2842..094fc9e 100644 --- a/crates/saft-macro/src/discover_tests.rs +++ b/crates/saft-macro/src/discover_tests.rs @@ -1,14 +1,21 @@ -use std::path::PathBuf; +use quote::{format_ident, quote}; +use std::{collections::HashMap, path::PathBuf}; use darling::{ast::NestedMeta, FromMeta}; use glob::glob; use proc_macro::TokenStream; -use quote::{format_ident, quote}; #[derive(FromMeta)] #[darling()] struct Args { - path: String, + root: String, + glob: String, +} + +#[derive(Debug)] +enum TestNode { + Node(HashMap), + Test(PathBuf), } pub fn expand_discover_tests( @@ -28,54 +35,126 @@ pub fn expand_discover_tests( }; let test_fn_ident = &test_fn.sig.ident.clone(); - let dirs = glob(&args.path).unwrap(); - - let tests = dirs - .map(|path| { - let path = path.unwrap(); - let file_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")) - .join("../..") - .join( - path.to_str() + let dirs = glob(PathBuf::from(&args.root).join(&args.glob).to_str().unwrap()).unwrap(); + + let mut root_node = TestNode::Node(HashMap::new()); + + for dir in dirs { + let path = dir.unwrap(); + + let root_s = PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .join("../..") + .join(&args.root) + .canonicalize() + .unwrap() + .to_str() + .unwrap() + .to_string(); + + let file_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .join("../..") + .join(&path) + .canonicalize() + .unwrap(); + + let parts = PathBuf::from((file_path.clone()).strip_prefix(&root_s).unwrap()) + .components() + .map(|c| c.as_os_str().to_str().unwrap().to_string()) + .collect::>(); + + let mut curr = &mut root_node; + + let parts = parts.iter().enumerate().collect::>(); + + for (i, part) in &parts { + let last = *i == parts.len() - 1; + + const CLASH_MSG: &'static str = "File and directory name clash, a directory and a name cannot exist with the same name in the same path"; + + let TestNode::Node(children) = curr else { + panic!("{}", CLASH_MSG) + }; + + if last { + let prev = children.insert(part.to_string(), TestNode::Test(file_path.clone())); + + if prev.is_some() { + panic!("{}", CLASH_MSG); + } + } else { + children + .entry(part.to_string()) + .or_insert(TestNode::Node(HashMap::new())); + + curr = if let TestNode::Node(children) = curr { + children.get_mut(*part).unwrap() + } else { + panic!("Was just inserted?") + }; + } + } + } + + fn create_tests( + node: &TestNode, + test_fn_ident: &proc_macro2::Ident, + ) -> proc_macro2::TokenStream { + match node { + TestNode::Node(children) => { + let mut toks = vec![]; + + for (dir, node) in children.iter() { + match node { + TestNode::Node(_) => { + let id = format_ident!("{}", dir); + let node_quote = create_tests(node, test_fn_ident); + toks.push(quote! { + mod #id { + #node_quote + } + + }) + } + TestNode::Test(_) => { + toks.push(create_tests(node, test_fn_ident)); + } + } + } + + quote!(#(#toks)*) + } + TestNode::Test(file_path) => { + let test_name = format_ident!( + "{}", + file_path + .file_name() + .unwrap() + .to_str() + .unwrap() + .to_string() + .split_once('.') .unwrap() + .0 .to_string() - .strip_suffix(".out") - .unwrap(), - ) - .to_str() - .unwrap() - .to_string(); - let file_expected = PathBuf::from(env!("CARGO_MANIFEST_DIR")) - .join("../..") - .join(path.clone()) - .to_str() - .unwrap() - .to_string(); - - let test_name = format_ident!( - "{}_{}", - test_fn_ident, - path.file_name() - .unwrap() - .to_str() - .unwrap() - .to_string() - .strip_suffix(".saf.out") - .unwrap() - ); - - quote! { - #[test] - fn #test_name() { - #test_fn_ident(#file_path, #file_expected); + ); + + let file_path_s = file_path.to_str().unwrap().to_string(); + + quote! { + #[test] + fn #test_name() { + #test_fn_ident(#file_path_s); + } } } - }) - .collect::>(); + } + } + + let tests = create_tests(&root_node, test_fn_ident); Ok(quote! { #test_fn - #(#tests)* + #tests }) } diff --git a/crates/saft-tests/res/tests/fib/fib.saf b/crates/saft-tests/res/tests/fib.saf similarity index 86% rename from crates/saft-tests/res/tests/fib/fib.saf rename to crates/saft-tests/res/tests/fib.saf index da64eb7..0e109ba 100644 --- a/crates/saft-tests/res/tests/fib/fib.saf +++ b/crates/saft-tests/res/tests/fib.saf @@ -11,3 +11,8 @@ fn fib(i) { print(fib(0)); print(fib(1)); print(fib(20)); + +# output: +# 0 +# 1 +# 6765 diff --git a/crates/saft-tests/res/tests/fib/fib.saf.out b/crates/saft-tests/res/tests/fib/fib.saf.out deleted file mode 100644 index ccf559c..0000000 --- a/crates/saft-tests/res/tests/fib/fib.saf.out +++ /dev/null @@ -1,3 +0,0 @@ -0 -1 -6765 diff --git a/crates/saft-tests/res/tests/fib_loop/fib_loop.saf b/crates/saft-tests/res/tests/fib_loop.saf similarity index 86% rename from crates/saft-tests/res/tests/fib_loop/fib_loop.saf rename to crates/saft-tests/res/tests/fib_loop.saf index 0bafca4..2a0119a 100644 --- a/crates/saft-tests/res/tests/fib_loop/fib_loop.saf +++ b/crates/saft-tests/res/tests/fib_loop.saf @@ -17,4 +17,7 @@ fn fib(n) { } } -print(fib(100)); +print(fib(20)); + +# output: +# 6765 diff --git a/crates/saft-tests/res/tests/fib_loop/fib_loop.out b/crates/saft-tests/res/tests/fib_loop/fib_loop.out deleted file mode 100644 index 63a38c8..0000000 --- a/crates/saft-tests/res/tests/fib_loop/fib_loop.out +++ /dev/null @@ -1 +0,0 @@ -3736710778780434371 diff --git a/crates/saft-tests/res/tests/loop/loop.saf.out b/crates/saft-tests/res/tests/loop/loop.saf.out deleted file mode 100644 index 1ff0ce0..0000000 --- a/crates/saft-tests/res/tests/loop/loop.saf.out +++ /dev/null @@ -1,4 +0,0 @@ -1 -2 -3 -13 diff --git a/crates/saft-tests/res/tests/loop/loop.saf b/crates/saft-tests/res/tests/loop_.saf similarity index 77% rename from crates/saft-tests/res/tests/loop/loop.saf rename to crates/saft-tests/res/tests/loop_.saf index 9e23449..4c31933 100644 --- a/crates/saft-tests/res/tests/loop/loop.saf +++ b/crates/saft-tests/res/tests/loop_.saf @@ -9,3 +9,9 @@ x := loop { }; print(x); + +# output: +# 1 +# 2 +# 3 +# 13 diff --git a/crates/saft-tests/res/tests/print/print.saf b/crates/saft-tests/res/tests/print.saf similarity index 55% rename from crates/saft-tests/res/tests/print/print.saf rename to crates/saft-tests/res/tests/print.saf index 492dc65..abef64c 100644 --- a/crates/saft-tests/res/tests/print/print.saf +++ b/crates/saft-tests/res/tests/print.saf @@ -4,3 +4,11 @@ print("asd"); print(print); print(1.0); print(1.1234); + +# output: +# 1 +# 2 +# asd +# +# 1.0 +# 1.1234 diff --git a/crates/saft-tests/res/tests/print/print.saf.out b/crates/saft-tests/res/tests/print/print.saf.out deleted file mode 100644 index 4ed4e42..0000000 --- a/crates/saft-tests/res/tests/print/print.saf.out +++ /dev/null @@ -1,6 +0,0 @@ -1 -2 -asd - -1.0 -1.1234 diff --git a/crates/saft-tests/src/lib.rs b/crates/saft-tests/src/lib.rs index 5ec0ded..d86f47f 100644 --- a/crates/saft-tests/src/lib.rs +++ b/crates/saft-tests/src/lib.rs @@ -4,15 +4,35 @@ mod test { use pretty_assertions::assert_eq; use saft_macro::discover_tests; - #[discover_tests(path = "./crates/saft-tests/res/tests/**/*.saf.out")] - fn test(file_name: &str, out_file: &str) { + #[discover_tests(root = "./crates/saft-tests/res/tests", glob = "**/[!_]*.saf")] + fn test(file_name: &str) { let mut cmd = Command::cargo_bin("saft").unwrap(); cmd.arg(file_name); - let output = cmd.unwrap(); + let got = String::from_utf8(cmd.unwrap().stdout) + .unwrap() + .lines() + .map(|line| line.trim()) + .collect::>() + .join("\n"); - let expected_output = std::fs::read_to_string(out_file).unwrap(); + let mut expected = Vec::new(); - assert_eq!(String::from_utf8(output.stdout).unwrap(), expected_output); + let mut got_output = false; + for line in std::fs::read_to_string(file_name).unwrap().lines() { + if line.starts_with('#') { + let comment = &line[1..].trim(); + + if got_output { + expected.push(comment.to_string()); + } else { + if *comment == "output:" { + got_output = true; + } + } + } + } + + assert_eq!(got, expected.join("\n")); } }