use rustine::exec::{execute, serialize_tree, serialize_tree_to_writer, RuntimeFormat};
use rustine::parser::lexer::lex;
use rustine::parser::syntax::parse_gel_document;
fn run(syntax: &str, input: &str, format: RuntimeFormat) -> String {
let tokens = lex(syntax).expect("lex");
let mut doc = parse_gel_document(&tokens).expect("parse");
let exec = execute(&mut doc, "input", input).expect("execute");
assert!(exec.error.is_none(), "execution error: {:?}", exec.error);
serialize_tree(&exec, format)
}
fn norm(s: &str) -> String {
s.trim_end().to_string()
}
const SIMPLE_SYNTAX: &str = include_str!("../fixtures/parity/simple/syntax1.gel");
const SIMPLE_INPUT: &str = include_str!("../fixtures/parity/simple/input1.txt");
const SIMPLE_JSON: &str = include_str!("../fixtures/parity/simple/output1.json");
const SIMPLE_XML: &str = include_str!("../fixtures/parity/simple/output1.xml");
#[test]
fn byte_exact_simple_json() {
let actual = run(SIMPLE_SYNTAX, SIMPLE_INPUT, RuntimeFormat::Json);
assert_eq!(norm(&actual), norm(SIMPLE_JSON), "simple JSON mismatch");
}
#[test]
fn byte_exact_simple_xml() {
let actual = run(SIMPLE_SYNTAX, SIMPLE_INPUT, RuntimeFormat::Xml);
assert_eq!(norm(&actual), norm(SIMPLE_XML), "simple XML mismatch");
}
const CSV_SYNTAX: &str = include_str!("../fixtures/parity/csv/syntax1.gel");
const CSV_INPUT: &str = include_str!("../fixtures/parity/csv/input1.txt");
const CSV_JSON: &str = include_str!("../fixtures/parity/csv/output1.json");
const CSV_XML: &str = include_str!("../fixtures/parity/csv/output1.xml");
#[test]
fn byte_exact_csv_json() {
let actual = run(CSV_SYNTAX, CSV_INPUT, RuntimeFormat::Json);
assert_eq!(norm(&actual), norm(CSV_JSON), "csv JSON mismatch");
}
#[test]
fn byte_exact_csv_xml() {
let actual = run(CSV_SYNTAX, CSV_INPUT, RuntimeFormat::Xml);
assert_eq!(norm(&actual), norm(CSV_XML), "csv XML mismatch");
}
const LS_SYNTAX: &str = include_str!("../fixtures/parity/linesplit/syntax1.gel");
const LS_INPUT: &str = include_str!("../fixtures/parity/linesplit/input1.txt");
const LS_JSON: &str = include_str!("../fixtures/parity/linesplit/output1.json");
const LS_XML: &str = include_str!("../fixtures/parity/linesplit/output1.xml");
#[test]
fn byte_exact_linesplit_json() {
let actual = run(LS_SYNTAX, LS_INPUT, RuntimeFormat::Json);
assert_eq!(norm(&actual), norm(LS_JSON), "linesplit JSON mismatch");
}
#[test]
fn byte_exact_linesplit_xml() {
let actual = run(LS_SYNTAX, LS_INPUT, RuntimeFormat::Xml);
assert_eq!(norm(&actual), norm(LS_XML), "linesplit XML mismatch");
}
const TRIA_SYNTAX: &str = include_str!("../fixtures/parity/tria/syntax1.gel");
const TRIA_INPUT: &str = include_str!("../fixtures/parity/tria/input1.txt");
const TRIA_JSON: &str = include_str!("../fixtures/parity/tria/output1.json");
const TRIA_XML: &str = include_str!("../fixtures/parity/tria/output1.xml");
#[test]
fn byte_exact_tria_json() {
let actual = run(TRIA_SYNTAX, TRIA_INPUT, RuntimeFormat::Json);
assert_eq!(norm(&actual), norm(TRIA_JSON), "tria JSON mismatch");
}
#[test]
fn byte_exact_tria_xml() {
let actual = run(TRIA_SYNTAX, TRIA_INPUT, RuntimeFormat::Xml);
assert_eq!(norm(&actual), norm(TRIA_XML), "tria XML mismatch");
}
const COMPLEX_SYNTAX: &str = include_str!("../fixtures/parity/complex/syntax1.gel");
const COMPLEX_INPUT: &str = include_str!("../fixtures/parity/complex/input1.txt");
const COMPLEX_JSON: &str = include_str!("../fixtures/parity/complex/output1.json");
const COMPLEX_XML: &str = include_str!("../fixtures/parity/complex/output1.xml");
#[test]
fn byte_exact_complex_json() {
let actual = run(COMPLEX_SYNTAX, COMPLEX_INPUT, RuntimeFormat::Json);
assert_large_eq(&actual, COMPLEX_JSON, "complex JSON");
}
#[test]
fn byte_exact_complex_xml() {
let actual = run(COMPLEX_SYNTAX, COMPLEX_INPUT, RuntimeFormat::Xml);
assert_large_eq(&actual, COMPLEX_XML, "complex XML");
}
fn assert_large_eq(actual: &str, expected: &str, label: &str) {
let actual_n = norm(actual);
let expected_n = norm(expected);
if actual_n == expected_n {
return;
}
let actual_lines: Vec<&str> = actual_n.lines().collect();
let expected_lines: Vec<&str> = expected_n.lines().collect();
for (i, (a, e)) in actual_lines.iter().zip(expected_lines.iter()).enumerate() {
if a != e {
let start = i.saturating_sub(3);
let end = (i + 4).min(actual_lines.len()).min(expected_lines.len());
let mut msg = format!(
"{label} mismatch at line {} (actual {} lines, expected {} lines):\n",
i + 1,
actual_lines.len(),
expected_lines.len()
);
for j in start..end {
let marker = if j == i { ">>>" } else { " " };
msg.push_str(&format!(
"{marker} actual[{j}]: {}\n{marker} expect[{j}]: {}\n",
actual_lines.get(j).unwrap_or(&"<EOF>"),
expected_lines.get(j).unwrap_or(&"<EOF>"),
));
}
panic!("{msg}");
}
}
panic!(
"{label} mismatch: actual has {} lines, expected has {} lines",
actual_lines.len(),
expected_lines.len()
);
}
fn run_exec(syntax: &str, input: &str) -> rustine::exec::ExecutionResult {
let tokens = lex(syntax).expect("lex");
let mut doc = parse_gel_document(&tokens).expect("parse");
let exec = execute(&mut doc, "input", input).expect("execute");
assert!(exec.error.is_none(), "execution error: {:?}", exec.error);
exec
}
#[test]
fn streaming_writer_matches_string_json() {
let exec = run_exec(SIMPLE_SYNTAX, SIMPLE_INPUT);
let string_out = serialize_tree(&exec, RuntimeFormat::Json);
let mut buf = Vec::new();
serialize_tree_to_writer(&exec, RuntimeFormat::Json, &mut buf).expect("write");
let writer_out = String::from_utf8(buf).expect("utf8");
assert_eq!(string_out, writer_out, "JSON streaming mismatch");
}
#[test]
fn streaming_writer_matches_string_xml() {
let exec = run_exec(SIMPLE_SYNTAX, SIMPLE_INPUT);
let string_out = serialize_tree(&exec, RuntimeFormat::Xml);
let mut buf = Vec::new();
serialize_tree_to_writer(&exec, RuntimeFormat::Xml, &mut buf).expect("write");
let writer_out = String::from_utf8(buf).expect("utf8");
assert_eq!(string_out, writer_out, "XML streaming mismatch");
}
#[test]
fn streaming_writer_matches_string_yaml() {
let exec = run_exec(SIMPLE_SYNTAX, SIMPLE_INPUT);
let string_out = serialize_tree(&exec, RuntimeFormat::Yaml);
let mut buf = Vec::new();
serialize_tree_to_writer(&exec, RuntimeFormat::Yaml, &mut buf).expect("write");
let writer_out = String::from_utf8(buf).expect("utf8");
assert_eq!(string_out, writer_out, "YAML streaming mismatch");
}