| fn main() { |
| generate_tests_from_spec() |
| } |
| |
| // If the "gen-tests" feature is absent, |
| // this function will be compiled down to nothing |
| #[cfg(not(feature = "gen-tests"))] |
| fn generate_tests_from_spec() {} |
| |
| // If the feature is present, generate tests |
| // from any .txt file present in the specs/ directory |
| // |
| // Test cases are present in the files in the |
| // following format: |
| // |
| // ```````````````````````````````` example |
| // markdown |
| // . |
| // expected html output |
| // ```````````````````````````````` |
| #[cfg(feature = "gen-tests")] |
| fn generate_tests_from_spec() { |
| use std::fs::{self, File}; |
| use std::io::{Read, Write}; |
| use std::path::PathBuf; |
| |
| // This is a hardcoded path to the CommonMark spec because it is not situated in |
| // the specs/ directory. It's in an array to easily chain it to the other iterator |
| // and make it easy to eventually add other hardcoded paths in the future if needed |
| let hardcoded = [ |
| "./third_party/CommonMark/spec.txt", |
| "./third_party/GitHub/gfm_table.txt", |
| "./third_party/GitHub/gfm_strikethrough.txt", |
| "./third_party/GitHub/gfm_tasklist.txt", |
| ]; |
| let hardcoded_iter = hardcoded.into_iter().map(PathBuf::from); |
| |
| // Create an iterator over the files in the specs/ directory that have a .txt extension |
| let spec_files = fs::read_dir("./specs") |
| .expect("Could not find the 'specs' directory") |
| .filter_map(Result::ok) |
| .map(|d| d.path()) |
| .filter(|p| p.extension().map(|e| e.to_owned()).is_some()) |
| .chain(hardcoded_iter) |
| .collect::<Vec<_>>(); |
| |
| for file_path in &spec_files { |
| let mut raw_spec = String::new(); |
| |
| File::open(&file_path) |
| .and_then(|mut f| f.read_to_string(&mut raw_spec)) |
| .expect("Could not read the spec file"); |
| |
| let rs_test_file = PathBuf::from("./tests/suite/") |
| .join(file_path.file_name().expect("Invalid filename")) |
| .with_extension("rs"); |
| |
| let mut spec_rs = |
| File::create(&rs_test_file).expect(&format!("Could not create {:?}", rs_test_file)); |
| |
| let spec_name = file_path.file_stem().unwrap().to_str().unwrap(); |
| |
| let spec = Spec::new(&raw_spec); |
| let mut n_tests = 0; |
| |
| spec_rs |
| .write(b"// This file is auto-generated by the build script\n") |
| .unwrap(); |
| spec_rs |
| .write(b"// Please, do not modify it manually\n") |
| .unwrap(); |
| spec_rs |
| .write(b"\nuse super::test_markdown_html;\n") |
| .unwrap(); |
| |
| for (i, testcase) in spec.enumerate() { |
| spec_rs |
| .write_fmt(format_args!( |
| r###" |
| #[test] |
| fn {}_test_{i}() {{ |
| let original = r##"{original}"##; |
| let expected = r##"{expected}"##; |
| |
| test_markdown_html(original, expected); |
| }} |
| "###, |
| spec_name, |
| i = i + 1, |
| original = testcase.original, |
| expected = testcase.expected |
| )) |
| .unwrap(); |
| |
| n_tests += 1; |
| } |
| |
| println!( |
| "cargo:warning=Generated {} tests in {:?}", |
| n_tests, rs_test_file |
| ); |
| } |
| |
| // write mods to suite/mod.rs |
| let suite_mod_file = PathBuf::from("./tests/suite/mod").with_extension("rs"); |
| |
| let mut mod_rs = |
| File::create(&suite_mod_file).expect(&format!("Could not create {:?}", &suite_mod_file)); |
| |
| mod_rs |
| .write(b"// This file is auto-generated by the build script\n") |
| .unwrap(); |
| mod_rs |
| .write(b"// Please, do not modify it manually\n") |
| .unwrap(); |
| mod_rs |
| .write(b"\npub use super::test_markdown_html;\n\n") |
| .unwrap(); |
| |
| for file_path in &spec_files { |
| let mod_name = file_path.file_stem().unwrap().to_str().unwrap(); |
| mod_rs.write(b"mod ").unwrap(); |
| mod_rs.write(mod_name.as_bytes()).unwrap(); |
| mod_rs.write(b";\n").unwrap(); |
| } |
| } |
| |
| #[cfg(feature = "gen-tests")] |
| pub struct Spec<'a> { |
| spec: &'a str, |
| } |
| |
| #[cfg(feature = "gen-tests")] |
| impl<'a> Spec<'a> { |
| pub fn new(spec: &'a str) -> Self { |
| Spec { spec: spec } |
| } |
| } |
| |
| #[cfg(feature = "gen-tests")] |
| pub struct TestCase { |
| pub original: String, |
| pub expected: String, |
| } |
| |
| #[cfg(feature = "gen-tests")] |
| impl<'a> Iterator for Spec<'a> { |
| type Item = TestCase; |
| |
| fn next(&mut self) -> Option<TestCase> { |
| let spec = self.spec; |
| |
| let i_start = match self |
| .spec |
| .find("```````````````````````````````` example\n") |
| .map(|pos| pos + 41) |
| { |
| Some(pos) => pos, |
| None => return None, |
| }; |
| |
| let i_end = match self.spec[i_start..] |
| .find("\n.\n") |
| .map(|pos| (pos + 1) + i_start) |
| { |
| Some(pos) => pos, |
| None => return None, |
| }; |
| |
| let e_end = match self.spec[i_end + 2..] |
| .find("````````````````````````````````\n") |
| .map(|pos| pos + i_end + 2) |
| { |
| Some(pos) => pos, |
| None => return None, |
| }; |
| |
| self.spec = &self.spec[e_end + 33..]; |
| |
| let test_case = TestCase { |
| original: spec[i_start..i_end].to_string().replace("→", "\t"), |
| expected: spec[i_end + 2..e_end].to_string().replace("→", "\t"), |
| }; |
| |
| Some(test_case) |
| } |
| } |