// #[macro_use] extern crate pretty_assertions; extern crate bumpalo; extern crate inlinable_string; extern crate roc_collections; extern crate roc_load; extern crate roc_module; #[macro_use] extern crate maplit; #[cfg(test)] mod cli_run { use cli_utils::helpers::{ example_file, extract_valgrind_errors, run_cmd, run_roc, run_with_valgrind, ValgrindError, ValgrindErrorXWhat, }; use std::fs; use std::path::Path; #[cfg(not(target_os = "macos"))] const ALLOW_VALGRIND: bool = true; // Disallow valgrind on macOS by default, because it reports a ton // of false positives. For local development on macOS, feel free to // change this to true! #[cfg(target_os = "macos")] const ALLOW_VALGRIND: bool = false; #[derive(Debug, PartialEq, Eq)] struct Example<'a> { filename: &'a str, executable_filename: &'a str, stdin: &'a [&'a str], expected_ending: &'a str, use_valgrind: bool, } fn check_output_with_stdin( file: &Path, stdin: &[&str], executable_filename: &str, flags: &[&str], expected_ending: &str, use_valgrind: bool, ) { let compile_out = run_roc(&[&["build", file.to_str().unwrap()], flags].concat()); if !compile_out.stderr.is_empty() { panic!("{}", compile_out.stderr); } assert!(compile_out.status.success(), "bad status {:?}", compile_out); let out = if use_valgrind && ALLOW_VALGRIND { let (valgrind_out, raw_xml) = run_with_valgrind( stdin, &[file.with_file_name(executable_filename).to_str().unwrap()], ); if valgrind_out.status.success() { let memory_errors = extract_valgrind_errors(&raw_xml).unwrap_or_else(|err| { panic!("failed to parse the `valgrind` xml output. Error was:\n\n{:?}\n\nvalgrind xml was: \"{}\"\n\nvalgrind stdout was: \"{}\"\n\nvalgrind stderr was: \"{}\"", err, raw_xml, valgrind_out.stdout, valgrind_out.stderr); }); if !memory_errors.is_empty() { for error in memory_errors { let ValgrindError { kind, what: _, xwhat, } = error; println!("Valgrind Error: {}\n", kind); if let Some(ValgrindErrorXWhat { text, leakedbytes: _, leakedblocks: _, }) = xwhat { println!(" {}", text); } } panic!("Valgrind reported memory errors"); } } else { let exit_code = match valgrind_out.status.code() { Some(code) => format!("exit code {}", code), None => "no exit code".to_string(), }; panic!("`valgrind` exited with {}. valgrind stdout was: \"{}\"\n\nvalgrind stderr was: \"{}\"", exit_code, valgrind_out.stdout, valgrind_out.stderr); } valgrind_out } else { run_cmd( file.with_file_name(executable_filename).to_str().unwrap(), stdin, &[], ) }; if !&out.stdout.ends_with(expected_ending) { panic!( "expected output to end with {:?} but instead got {:#?}", expected_ending, out ); } assert!(out.status.success()); } #[test] fn check_all_examples() { let mut all_examples = hashmap! { "hello-world" => vec![ Example { filename: "Hello.roc", executable_filename: "hello-world", stdin: &[], expected_ending:"Hello, World!!!!!!!!!!!!!\n", use_valgrind: true, } ], "quicksort" => vec![ Example { filename: "Quicksort.roc", executable_filename: "quicksort", stdin: &[], expected_ending: "[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2]\n", use_valgrind: true, } ], "shared-quicksort" => vec![ Example { filename: "Quicksort.roc", executable_filename: "quicksort", stdin: &[], expected_ending: "[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2]\n", use_valgrind: true, } ], "effect" => vec![ Example { filename: "Main.roc", executable_filename: "effect-example", stdin: &["hi there!"], expected_ending: "hi there!\n", use_valgrind: true, } ], "multi-dep-str" => vec![ Example { filename: "Main.roc", executable_filename: "multi-dep-str", stdin: &[], expected_ending: "I am Dep2.str2\n", use_valgrind: true, } ], "multi-dep-thunk" => vec![ Example { filename: "Main.roc", executable_filename: "multi-dep-thunk", stdin: &[], expected_ending: "I am Dep2.value2\n", use_valgrind: true, } ], "tea" => vec![ Example { filename: "Main.roc", executable_filename: "tea-example", stdin: &[], expected_ending: "", use_valgrind: true, } ], "cli" => vec![ Example { filename: "Echo.roc", executable_filename: "echo", stdin: &["Giovanni\n", "Giorgio\n"], expected_ending: "Giovanni Giorgio!\n", use_valgrind: true, } ], "custom-malloc" => vec![ Example { filename: "Main.roc", executable_filename: "custom-malloc-example", stdin: &[], expected_ending: "ms!\nThe list was small!\n", use_valgrind: true, } ], "task" => vec![ Example { filename: "Main.roc", executable_filename: "task-example", stdin: &[], expected_ending: "successfully wrote to file\n", use_valgrind: true, } ], "benchmarks" => vec![ Example { filename: "NQueens.roc", executable_filename: "nqueens", stdin: &[], expected_ending: "4\n", use_valgrind: true, }, Example { filename: "CFold.roc", executable_filename: "cfold", stdin: &[], expected_ending: "11 & 11\n", use_valgrind: true, }, Example { filename: "Deriv.roc", executable_filename: "deriv", stdin: &[], expected_ending: "1 count: 6\n2 count: 22\n", use_valgrind: true, }, Example { filename: "RBTreeInsert.roc", executable_filename: "rbtree-insert", stdin: &[], expected_ending: "Node Black 0 {} Empty Empty\n", use_valgrind: true, }, Example { filename: "RBTreeDel.roc", executable_filename: "rbtree-del", stdin: &[], expected_ending: "30\n", use_valgrind: true, }, Example { filename: "TestAStar.roc", executable_filename: "test-astar", stdin: &[], expected_ending: "True\n", use_valgrind: false, }, Example { filename: "TestBase64.roc", executable_filename: "test-base64", stdin: &[], expected_ending: "encoded: SGVsbG8gV29ybGQ=\ndecoded: Hello World\n", use_valgrind: true, }, Example { filename: "Closure.roc", executable_filename: "closure", stdin: &[], expected_ending: "", use_valgrind: true, } ], }; for entry in fs::read_dir("../examples").unwrap() { let entry = entry.unwrap(); if entry.file_type().unwrap().is_dir() { let dir_name = entry.file_name().into_string().unwrap(); let examples = all_examples.remove(dir_name.as_str()).unwrap_or(Vec::new()); if examples.is_empty() { panic!("The directory examples/{} does not have any corresponding tests in cli_run. Please add one, so if it ever stops compiling, we'll know about it right away!", dir_name); } for example in examples { println!( "Running {:?}...", &example_file(dir_name.as_str(), example.filename), ); // Check with and without optimizations check_output_with_stdin( &example_file(dir_name.as_str(), example.filename), example.stdin, example.executable_filename, &[], example.expected_ending, example.use_valgrind, ); check_output_with_stdin( &example_file(dir_name.as_str(), example.filename), example.stdin, example.executable_filename, &["--optimize"], example.expected_ending, example.use_valgrind, ); } } } assert_eq!(all_examples, std::collections::HashMap::default()); } #[serial(hello_world)] fn run_hello_world() { check_output( &example_file("hello-world", "Hello.roc"), "hello-world", &[], "Hello, World!\n", true, ); } #[test] #[serial(hello_world)] fn run_hello_world_optimized() { check_output( &example_file("hello-world", "Hello.roc"), "hello-world", &[], "Hello, World!\n", true, ); } #[test] #[serial(quicksort)] fn run_quicksort_not_optimized() { check_output( &example_file("quicksort", "Quicksort.roc"), "quicksort", &[], "[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2]\n", true, ); } #[test] #[serial(quicksort)] fn run_quicksort_optimized() { check_output( &example_file("quicksort", "Quicksort.roc"), "quicksort", &["--optimize"], "[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2]\n", true, ); } #[test] #[serial(quicksort)] fn run_quicksort_optimized_valgrind() { check_output( &example_file("quicksort", "Quicksort.roc"), "quicksort", &["--optimize"], "[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2]\n", true, ); } #[test] #[serial(nqueens)] fn run_nqueens_not_optimized() { check_output_with_stdin( &example_file("benchmarks", "NQueens.roc"), "6", "nqueens", &[], "4\n", true, ); } #[test] #[serial(cfold)] fn run_cfold_not_optimized() { check_output_with_stdin( &example_file("benchmarks", "CFold.roc"), "3", "cfold", &[], "11 & 11\n", true, ); } #[test] #[serial(deriv)] fn run_deriv_not_optimized() { check_output_with_stdin( &example_file("benchmarks", "Deriv.roc"), "2", "deriv", &[], "1 count: 6\n2 count: 22\n", true, ); } #[test] #[serial(deriv)] fn run_rbtree_insert_not_optimized() { check_output( &example_file("benchmarks", "RBTreeInsert.roc"), "rbtree-insert", &[], "Node Black 0 {} Empty Empty\n", true, ); } #[test] #[serial(deriv)] fn run_rbtree_delete_not_optimized() { check_output_with_stdin( &example_file("benchmarks", "RBTreeDel.roc"), "420", "rbtree-del", &[], "30\n", true, ); } #[test] #[serial(astar)] fn run_astar_optimized_1() { check_output( &example_file("benchmarks", "TestAStar.roc"), "test-astar", &[], "True\n", false, ); } #[test] #[serial(base64)] fn base64() { check_output( &example_file("benchmarks", "TestBase64.roc"), "test-base64", &[], "encoded: SGVsbG8gV29ybGQ=\ndecoded: Hello World\n", true, ); } #[test] #[serial(closure)] fn closure() { check_output( &example_file("benchmarks", "Closure.roc"), "closure", &[], "", true, ); } // #[test] // #[serial(effect)] // fn run_effect_unoptimized() { // check_output( // &example_file("effect", "Main.roc"), // &[], // "I am Dep2.str2\n", // true, // ); // } #[test] #[serial(multi_dep_str)] fn run_multi_dep_str_unoptimized() { check_output( &fixture_file("multi-dep-str", "Main.roc"), "multi-dep-str", &[], "I am Dep2.str2\n", true, ); } #[test] #[serial(multi_dep_str)] fn run_multi_dep_str_optimized() { check_output( &fixture_file("multi-dep-str", "Main.roc"), "multi-dep-str", &["--optimize"], "I am Dep2.str2\n", true, ); } #[test] #[serial(multi_dep_thunk)] fn run_multi_dep_thunk_unoptimized() { check_output( &fixture_file("multi-dep-thunk", "Main.roc"), "multi-dep-thunk", &[], "I am Dep2.value2\n", true, ); } #[test] #[serial(multi_dep_thunk)] fn run_multi_dep_thunk_optimized() { check_output( &fixture_file("multi-dep-thunk", "Main.roc"), "multi-dep-thunk", &["--optimize"], "I am Dep2.value2\n", true, ); } #[test] #[serial(effect)] fn run_effect() { check_output_with_stdin( &example_file("effect", "Main.roc"), "hello world how are you", "effect-example", &[], "hello world how are you\n", true, ); } }