#[macro_use] extern crate pretty_assertions; #[macro_use] extern crate indoc; #[cfg(not(debug_assertions))] extern crate roc_collections; mod helpers; #[cfg(test)] mod bindgen_cli_run { use crate::helpers::fixtures_dir; use cli_utils::helpers::{run_bindgen, run_roc, Out}; use std::fs; use std::path::Path; #[derive(Debug, PartialEq, Eq)] struct Example<'a> { filename: &'a str, executable_filename: &'a str, stdin: &'a [&'a str], input_file: Option<&'a str>, expected_ending: &'a str, use_valgrind: bool, } /// This macro does two things. /// /// First, it generates and runs a separate test for each of the given /// expected stdout endings. Each of these should test a particular .roc file /// in the fixtures/ directory. /// /// Second, it generates an extra test which (non-recursively) traverses the /// fixtures/ directory and verifies that each of the .roc files in there /// has had a corresponding test generated in the previous step. This test /// will fail if we ever add a new .roc file to fixtures/ and forget to /// add a test for it here! macro_rules! fixtures { ($($test_name:ident:$fixture_dir:expr => $ends_with:expr,)+) => { $( #[test] #[allow(non_snake_case)] fn $test_name() { let dir = fixtures_dir($fixture_dir); generate_bindings_for(&dir.join("platform"), std::iter::empty()); let out = run_app(&dir.join("app.roc"), std::iter::empty()); assert!(out.status.success()); assert_eq!(out.stderr, ""); assert!( out.stdout.ends_with($ends_with), "Unexpected stdout ending - expected {:?} but stdout was: {:?}", $ends_with, out.stdout ); } )* #[test] #[cfg(not(debug_assertions))] fn all_examples_have_tests() { use roc_collections::all::VecSet; let mut all_examples: VecSet<&str, &str> = VecSet::default(); $( all_examples.insert($fixture_dir); )* check_for_tests(&mut all_examples); } } } fixtures! { basic_record:"basic-record" => "Record was: MyRcd { b: 42, a: 1995 }\n", } #[cfg(not(debug_assertions))] fn check_for_tests(all_examples: &mut roc_collections::all::VecSet<&str>) { use roc_collections::all::VecSet; let fixtures_dir = fixtures_dir(".").with_file_name(""); let entries = std::fs::read_dir(fixtures_dir).unwrap_or_else(|err| { panic!( "Error trying to read {} as an examples directory: {}", examples_dir, err ); }); for entry in entries { let entry = entry.unwrap(); if entry.file_type().unwrap().is_dir() { let fixture_dir_name = entry.file_name().into_string().unwrap(); all_examples.remove(fixture_dir_name).unwrap_or_else(|| { panic!("The bindgen fixture directory {} does not have any corresponding tests in cli_run. Please add one, so if it ever stops working, we'll know about it right away!", entry.path()); }); } } assert_eq!(all_examples, &mut VecSet::default()); } fn generate_bindings_for<'a, I: IntoIterator>( platform_dir: &'a Path, args: I, ) -> Out { let package_config = platform_dir.join("Package-Config.roc"); let bindings_file = platform_dir.join("src").join("bindings.rs"); // Delete the bindings file to make sure we're actually regenerating it! if bindings_file.exists() { fs::remove_file(&bindings_file) .expect("Unable to remove bindings.rs in order to regenerate it in the test"); } // Generate a fresh bindings.rs for this platform let bindgen_out = run_bindgen( // converting these all to String avoids lifetime issues args.into_iter().map(|arg| arg.to_string()).chain([ package_config.to_str().unwrap().to_string(), bindings_file.to_str().unwrap().to_string(), ]), ); // If there is any stderr, it should be reporting the runtime and that's it! if !(bindgen_out.stderr.is_empty() || bindgen_out.stderr.starts_with("runtime: ") && bindgen_out.stderr.ends_with("ms\n")) { panic!( "`roc-bindgen` command had unexpected stderr: {}", bindgen_out.stderr ); } assert!(bindgen_out.status.success(), "bad status {:?}", bindgen_out); bindgen_out } fn run_app<'a, I: IntoIterator>(app_file: &'a Path, args: I) -> Out { // Generate bindings.rs for this platform let compile_out = run_roc( // converting these all to String avoids lifetime issues args.into_iter() .map(|arg| arg.to_string()) .chain([app_file.to_str().unwrap().to_string()]), &[], ); // If there is any stderr, it should be reporting the runtime and that's it! if !(compile_out.stderr.is_empty() || compile_out.stderr.starts_with("runtime: ") && compile_out.stderr.ends_with("ms\n")) { panic!( "`roc` command had unexpected stderr: {}", compile_out.stderr ); } assert!(compile_out.status.success(), "bad status {:?}", compile_out); compile_out } }