use std::{ borrow::Cow, collections::BTreeMap, path::{Path, PathBuf}, }; use grass::Fs; #[macro_export] macro_rules! test { (@base $( #[$attr:meta] ),*$func:ident, $input:expr, $output:expr, $options:expr) => { $(#[$attr])* #[test] #[allow(non_snake_case)] fn $func() { let sass = grass::from_string($input.to_string(), &$options) .expect(concat!("failed to parse on ", $input)); assert_eq!( String::from($output), sass ); } }; ($( #[$attr:meta] ),*$func:ident, $input:expr, $output:expr, $options:expr) => { test!(@base $(#[$attr])* $func, $input, $output, $options); }; ($( #[$attr:meta] ),*$func:ident, $input:expr, $output:expr) => { test!(@base $(#[$attr])* $func, $input, $output, grass::Options::default()); }; } /// Verify the error *message* /// Span and scope information are not yet tested #[macro_export] macro_rules! error { (@base $( #[$attr:meta] ),*$func:ident, $input:expr, $err:expr, $options:expr) => { $(#[$attr])* #[test] #[allow(non_snake_case)] fn $func() { match grass::from_string($input.to_string(), &$options) { Ok(..) => panic!("did not fail"), Err(e) => assert_eq!($err, e.to_string() .chars() .take_while(|c| *c != '\n') .collect::() .as_str() ), } } }; ($( #[$attr:meta] ),*$func:ident, $input:expr, $err:expr) => { error!(@base $(#[$attr])* $func, $input, $err, grass::Options::default()); }; ($( #[$attr:meta] ),*$func:ident, $input:expr, $err:expr, $options:expr) => { error!(@base $(#[$attr])* $func, $input, $err, $options); }; } /// Create a temporary file with the given name /// and contents. /// /// This must be a macro rather than a function /// because the tempfile will be deleted when it /// exits scope #[macro_export] macro_rules! tempfile { ($name:literal, $content:literal) => { let mut f = tempfile::Builder::new() .rand_bytes(0) .prefix("") .suffix($name) .tempfile_in("") .unwrap(); write!(f, "{}", $content).unwrap(); }; ($name:literal, $content:literal, dir=$dir:literal) => { let _d = if !std::path::Path::new($dir).is_dir() { Some( tempfile::Builder::new() .rand_bytes(0) .prefix("") .suffix($dir) .tempdir_in("") .unwrap(), ) } else { None }; let mut f = tempfile::Builder::new() .rand_bytes(0) .prefix("") .suffix($name) .tempfile_in($dir) .unwrap(); write!(f, "{}", $content).unwrap(); }; } #[macro_export] macro_rules! assert_err { ($err:literal, $input:expr) => { match grass::from_string($input.to_string(), &grass::Options::default()) { Ok(..) => panic!("did not fail"), Err(e) => assert_eq!( $err, e.to_string() .chars() .take_while(|c| *c != '\n') .collect::() .as_str() ), } }; ($input:expr, $err:expr, $options:expr) => { match grass::from_string($input.to_string(), &$options) { Ok(..) => panic!("did not fail"), Err(e) => assert_eq!( $err, e.to_string() .chars() .take_while(|c| *c != '\n') .collect::() .as_str() ), } }; } /// Suitable for simple import tests. Does not properly implement path resolution -- /// paths like `a/../b` will not work #[derive(Debug)] pub struct TestFs { files: BTreeMap>, } #[allow(unused)] impl TestFs { pub fn new() -> Self { Self { files: BTreeMap::new(), } } pub fn add_file(&mut self, name: &'static str, contents: &'static str) { self.files .insert(PathBuf::from(name), Cow::Borrowed(contents)); } } #[allow(unused)] impl Fs for TestFs { fn is_file(&self, path: &Path) -> bool { self.files.contains_key(path) } fn is_dir(&self, path: &Path) -> bool { false } fn read(&self, path: &Path) -> std::io::Result> { Ok(self.files.get(path).unwrap().as_bytes().to_vec()) } }