/// This is a practically direct rip of Manuel Ceron's awesome integration test /// script from his Lox project, Loxido. /// /// @see https://github.com/ceronman/loxido/blob/master/tests/integration.rs /// /// Copyright 2020 Manuel CerĂ³n /// /// Permission is hereby granted, free of charge, to any person obtaining a copy of /// this software and associated documentation files (the "Software"), to deal in /// the Software without restriction, including without limitation the rights to /// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies /// of the Software, and to permit persons to whom the Software is furnished to do /// so, subject to the following conditions: /// /// The above copyright notice and this permission notice shall be included in all /// copies or substantial portions of the Software. /// /// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR /// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, /// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE /// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER /// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, /// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE /// SOFTWARE. use std::path::PathBuf; use std::{env, fs, process::Command}; use regex::Regex; extern crate test_generator; use test_generator::test_resources; fn test_command() -> Command { // Create full path to binary let mut path = env::current_exe() .unwrap() .parent() .unwrap() .parent() .unwrap() .to_owned(); path.push("blackpool"); path.set_extension(env::consts::EXE_EXTENSION); Command::new(path.into_os_string()) } struct RuntimeError { line_prefix: String, message: String, } struct Expected { out: Vec, compile_err: Vec, runtime_err: Option, } fn parse_comments(path: &PathBuf) -> Expected { let output_re = Regex::new(r"// expect: ?(.*)").unwrap(); let error_re = Regex::new(r"// (Error.*)").unwrap(); let error_line_re = Regex::new(r"// \[(?:c )?line (\d+)\] (Error.*)").unwrap(); let runtime_error_re = Regex::new(r"// expect runtime error: (.+)").unwrap(); let mut expected = Expected { out: vec![], compile_err: vec![], runtime_err: None, }; println!("PATH: {}", path.display()); let content = fs::read_to_string(path).unwrap(); for (i, line) in content.lines().enumerate() { if let Some(m) = output_re.captures(line) { let s = m.get(1).unwrap().as_str().to_owned(); expected.out.push(s); } if let Some(m) = error_line_re.captures(line) { let line = m.get(1).unwrap().as_str(); let msg = m.get(2).unwrap().as_str(); let s = format!("[line {}] {}", line, msg); expected.compile_err.push(s); } if let Some(m) = error_re.captures(line) { let msg = m.get(1).unwrap().as_str(); let s = format!("[line {}] {}", i + 1, msg); expected.compile_err.push(s); } if let Some(m) = runtime_error_re.captures(line) { let message = m.get(1).unwrap().as_str().to_owned(); let line_prefix = format!("[line {}]", i + 1); expected.runtime_err = Some(RuntimeError { line_prefix, message }); } } expected } #[test_resources("tests/scripting_language/integration/assignment/*.lox")] #[test_resources("tests/scripting_language/integration/block/*.lox")] #[test_resources("tests/scripting_language/integration/bool/*.lox")] #[test_resources("tests/scripting_language/integration/call/*.lox")] #[test_resources("tests/scripting_language/integration/class/*.lox")] #[test_resources("tests/scripting_language/integration/closure/*.lox")] #[test_resources("tests/scripting_language/integration/comments/*.lox")] #[test_resources("tests/scripting_language/integration/constructor/*.lox")] #[test_resources("tests/scripting_language/integration/field/*.lox")] #[test_resources("tests/scripting_language/integration/for/*.lox")] #[test_resources("tests/scripting_language/integration/function/*.lox")] #[test_resources("tests/scripting_language/integration/if/*.lox")] #[test_resources("tests/scripting_language/integration/inheritance/*.lox")] // Skipping these for now (forever?). // #[test_resources("tests/scripting_language/integration/limit/*.lox")] #[test_resources("tests/scripting_language/integration/logical_operator/*.lox")] #[test_resources("tests/scripting_language/integration/method/*.lox")] #[test_resources("tests/scripting_language/integration/nil/*.lox")] #[test_resources("tests/scripting_language/integration/number/*.lox")] #[test_resources("tests/scripting_language/integration/operator/*.lox")] #[test_resources("tests/scripting_language/integration/print/*.lox")] #[test_resources("tests/scripting_language/integration/regression/*.lox")] #[test_resources("tests/scripting_language/integration/return/*.lox")] #[test_resources("tests/scripting_language/integration/string/*.lox")] #[test_resources("tests/scripting_language/integration/super/*.lox")] #[test_resources("tests/scripting_language/integration/this/*.lox")] #[test_resources("tests/scripting_language/integration/variable/*.lox")] #[test_resources("tests/scripting_language/integration/while/*.lox")] fn run_file_test(filename: &str) { let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); path.push(filename.replace("crates/hornvale/", "")); let expected = parse_comments(&path); let output = test_command().arg(path).output().unwrap(); let out: Vec = String::from_utf8(output.stdout) .unwrap() .lines() .map(|x| x.to_owned()) .collect(); let err: Vec = String::from_utf8(output.stderr) .unwrap() .lines() .map(|x| x.to_owned()) .collect(); match (expected.runtime_err.is_none(), expected.compile_err.is_empty()) { (true, true) => assert!(output.status.success(), "Program exited with failure, expected success"), (false, true) => assert_eq!( output.status.code().unwrap(), 70, "Runtime errors should have error code 70" ), (true, false) => assert_eq!( output.status.code().unwrap(), 65, "Compile errors should have error code 65" ), (false, false) => panic!("Simultaneous error and compile error"), } if let Some(e) = expected.runtime_err { assert!( err.len() > 0, "Should have a message like {}, had diddly-squat", e.message ); assert_eq!(e.message, err[0], "Runtime error should match"); assert_eq!( err[1][0..e.line_prefix.len()], e.line_prefix, "Runtime error line should match" ); } else { if !err.is_empty() { println!("err: {:#?}", err); assert_eq!( output.status.code().unwrap(), 65, "Compile errors should have error code 65" ); } assert_eq!(expected.compile_err.get(0), err.get(0), "Compile error should match"); } assert_eq!(expected.out, out, "Output should match"); }