Skip to content

Commit 8b6acd8

Browse files
authored
Update test generation to run strict eval and static analysis tests (#392)
1 parent 67777c2 commit 8b6acd8

File tree

4 files changed

+159
-139
lines changed

4 files changed

+159
-139
lines changed

partiql-conformance-test-generator/src/generator.rs

Lines changed: 129 additions & 122 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
use crate::schema::spec::*;
22
use crate::schema::structure::*;
33

4-
use crate::util::Escaper;
4+
use crate::util::{escape_fn_code, Escaper};
55
use codegen::{Function, Module, Scope};
66
use ion_rs::TextWriterBuilder;
77

88
use ion_rs::element::writer::ElementWriter;
99
use ion_rs::element::{Element, Struct};
10+
use quote::__private::TokenStream;
1011
use quote::quote;
1112
use std::collections::{HashMap, HashSet};
1213

@@ -344,20 +345,8 @@ impl Generator {
344345
name
345346
}
346347

347-
fn gen_test(&mut self, scope: &mut Scope, test_case: &TestCase) {
348-
let escaped_name = test_case.name.escape_test_name();
349-
let name = self.intern_test_name(escaped_name);
350-
351-
let test_fn: &mut Function = scope.new_fn(&name);
352-
test_fn.attr("test");
353-
test_fn.attr("allow(text_direction_codepoint_in_literal)");
354-
355-
let doc = format!("Generated test for test named `{}`", &test_case.name);
356-
test_fn.doc(&doc);
357-
358-
let mut ignore_test = false;
359-
let mut has_env = false;
360-
let test_case_expr = |gen: &dyn Fn(&str) -> _| match &test_case.statement {
348+
fn test_case_stmts(&self, test_case: &TestCase) -> Vec<String> {
349+
match &test_case.statement {
361350
TestStatement::EquivalenceClass(equiv_id) => {
362351
let stmts = self
363352
.curr_equivs
@@ -367,159 +356,177 @@ impl Generator {
367356
.next();
368357

369358
let stmts = stmts.expect("equivalence class named");
370-
stmts.iter().map(|s| gen(s)).collect::<Vec<_>>()
359+
stmts.to_vec()
371360
}
372-
TestStatement::Statement(s) => vec![gen(s)],
361+
TestStatement::Statement(s) => vec![s.clone()],
362+
}
363+
}
364+
365+
fn create_test<'a>(
366+
&mut self,
367+
scope: &'a mut Scope,
368+
test_case: &TestCase,
369+
name_prefix: Option<&str>,
370+
needs_env: bool,
371+
) -> &'a mut Function {
372+
let bare_name = &test_case.name;
373+
let prefixed_name = if let Some(prefix) = name_prefix {
374+
format!("{prefix}_{bare_name}")
375+
} else {
376+
bare_name.clone()
373377
};
378+
let escaped_name = prefixed_name.escape_test_name();
379+
let name = self.intern_test_name(escaped_name);
374380

375-
if let Some(env) = &test_case.env {
381+
let test_fn: &mut Function = scope.new_fn(&name);
382+
test_fn.attr("test");
383+
test_fn.attr("allow(text_direction_codepoint_in_literal)");
384+
385+
let doc = format!("Generated test for test named `{}`", &test_case.name);
386+
test_fn.doc(&doc);
387+
388+
let env = if let Some(env) = &test_case.env {
376389
let env = struct_to_string(env);
377-
let env = quote! {
390+
quote! {
378391
let env_ion_text = #env;
379392
let env = Some(env_ion_text.into());
380393
}
381-
.to_string()
382-
.replace("\\n", "\n");
383-
test_fn.line(env);
384-
has_env = true;
394+
} else if needs_env {
395+
quote! {
396+
let env = environment();
397+
}
398+
} else {
399+
quote! {}
400+
};
401+
402+
test_fn.line(escape_fn_code(env));
403+
404+
test_fn
405+
}
406+
407+
fn write_aside_expected(&mut self, test_case: &TestCase, expected: String) -> (String, bool) {
408+
if expected.lines().count() > EXPECTED_INLINE_LOWER_BOUND_LINE_COUNT {
409+
let expected_file = self
410+
.curr_mod_path
411+
.iter()
412+
.map(|s| s.escape_path())
413+
.chain(std::iter::once(test_case.name.escape_path()))
414+
.collect::<Vec<_>>()
415+
.join("___")
416+
+ ".expected.ion";
417+
418+
let td_dir = TEST_DATA_DIR.to_string();
419+
let expected_path: Vec<_> = self
420+
.curr_path
421+
.iter()
422+
.chain(std::iter::once(&td_dir))
423+
.chain(std::iter::once(&expected_file))
424+
.collect();
425+
426+
self.result.insert(
427+
expected_path.as_slice(),
428+
Node::Value(TestValueNode { value: expected }),
429+
);
430+
431+
(format!("{TEST_DATA_DIR}/{expected_file}"), true)
432+
} else {
433+
(expected, false)
434+
}
435+
}
436+
437+
fn gen_test(&mut self, scope: &mut Scope, test_case: &TestCase) {
438+
let stmts = self.test_case_stmts(test_case);
439+
let test_case_expr =
440+
|gen: &dyn Fn(&str) -> _| stmts.iter().map(|s| gen(s)).collect::<Vec<_>>();
441+
442+
fn mode_data(eval_mode: &EvaluationMode) -> (&'static str, TokenStream) {
443+
match eval_mode {
444+
EvaluationMode::EvalModeError => ("strict", quote! { EvaluationMode::Error }),
445+
EvaluationMode::EvalModeCoerce => ("permissive", quote! { EvaluationMode::Coerce }),
446+
}
385447
}
386448

387449
for assertion in &test_case.assert {
388450
match assertion {
389451
Assertion::SyntaxSuccess(_) => {
452+
let test_fn = self.create_test(scope, test_case, None, false);
390453
let stmts = test_case_expr(&|stmt: &str| quote! {pass_syntax(#stmt);});
391-
let tokens = quote! {
454+
test_fn.line(escape_fn_code(quote! {
392455
#(#stmts)*
393-
};
394-
test_fn.line(tokens.to_string().replace("\\n", "\n"));
456+
}));
395457
}
396458
Assertion::SyntaxFail(_) => {
459+
let test_fn = self.create_test(scope, test_case, None, false);
397460
let stmts = test_case_expr(&|stmt: &str| quote! {fail_syntax(#stmt);});
398-
let tokens = quote! {
461+
test_fn.line(escape_fn_code(quote! {
399462
#(#stmts)*
400-
};
401-
test_fn.line(tokens.to_string().replace("\\n", "\n"));
463+
}));
402464
}
403465
Assertion::StaticAnalysisFail(_) => {
404-
// TODO semantics tests are not yet implemented
405-
ignore_test = true;
466+
let test_fn = self.create_test(scope, test_case, None, false);
406467

407468
let stmts = test_case_expr(&|stmt: &str| quote! {fail_semantics(#stmt);});
408-
let tokens = quote! {
469+
test_fn.line(escape_fn_code(quote! {
409470
#(#stmts)*
410-
};
411-
test_fn.line(tokens.to_string().replace("\\n", "\n"));
471+
}));
412472
}
413473
Assertion::EvaluationSuccess(EvaluationSuccessAssertion {
414474
output,
415475
eval_mode,
416476
..
417477
}) => {
418-
if !std::mem::replace(&mut has_env, true) {
419-
test_fn.line("let env = environment();\n\n");
420-
}
421-
test_fn.line("\n//**** evaluation success test case(s) ****//");
422-
423-
let expected = elt_to_string(output);
424-
let expected =
425-
if expected.lines().count() > EXPECTED_INLINE_LOWER_BOUND_LINE_COUNT {
426-
let expected_file = self
427-
.curr_mod_path
428-
.iter()
429-
.map(|s| s.escape_path())
430-
.chain(std::iter::once(test_case.name.escape_path()))
431-
.collect::<Vec<_>>()
432-
.join("___")
433-
+ ".expected.ion";
434-
435-
let td_dir = TEST_DATA_DIR.to_string();
436-
let expected_path: Vec<_> = self
437-
.curr_path
438-
.iter()
439-
.chain(std::iter::once(&td_dir))
440-
.chain(std::iter::once(&expected_file))
441-
.collect();
442-
443-
self.result.insert(
444-
expected_path.as_slice(),
445-
Node::Value(TestValueNode { value: expected }),
446-
);
447-
448-
let data_file = format!("{TEST_DATA_DIR}/{expected_file}");
449-
quote! {include_str!(#data_file)}
478+
for mode in eval_mode {
479+
let (prefix, mode) = mode_data(mode);
480+
481+
let test_fn = self.create_test(scope, test_case, Some(prefix), true);
482+
test_fn.line("\n//**** evaluation success test case(s) ****//");
483+
484+
let (expected, is_file) =
485+
self.write_aside_expected(test_case, elt_to_string(output));
486+
let expected = if is_file {
487+
quote! {include_str!(#expected)}
450488
} else {
451489
quote! {#expected}
452490
};
453491

454-
let modes: Vec<_> = eval_mode
455-
.into_iter()
456-
.map(|mode| match mode {
457-
EvaluationMode::EvalModeError => quote! { EvaluationMode::Error },
458-
EvaluationMode::EvalModeCoerce => quote! { EvaluationMode::Coerce },
459-
})
460-
.collect();
461-
462-
// emit asserts for all statements X all modes
463-
let stmts = test_case_expr(&|stmt: &str| {
464-
// emit one assert statement per evaluation mode
465-
let asserts = modes.iter().map(|mode| {
492+
// emit asserts for all statements
493+
let stmts = test_case_expr(&|stmt: &str| {
494+
// emit PartiQL statement and evaluation mode assert
466495
quote! {
496+
let stmt = #stmt;
467497
pass_eval(stmt, #mode, &env, &expected);
468498
}
469499
});
470-
// emit PartiQL statement and evaluation mode asserts
471-
quote! {
472-
let stmt = #stmt;
473-
#(#asserts)*
474-
}
475-
});
476-
477-
let tokens = quote! {
478-
let expected = #expected.into();
479-
#(#stmts)*
480-
};
481-
test_fn.line(tokens.to_string().replace("\\n", "\n"));
500+
501+
test_fn.line(escape_fn_code(quote! {
502+
let expected = #expected.into();
503+
#(#stmts)*
504+
}));
505+
}
482506
}
483507
Assertion::EvaluationFail(EvaluationFailAssertion { eval_mode, .. }) => {
484-
if !std::mem::replace(&mut has_env, true) {
485-
test_fn.line("let env = environment();\n\n");
486-
}
487-
test_fn.line("\n//**** evaluation failure test case(s) ****//");
488-
489-
let modes: Vec<_> = eval_mode
490-
.into_iter()
491-
.map(|mode| match mode {
492-
EvaluationMode::EvalModeError => quote! { EvaluationMode::Error },
493-
EvaluationMode::EvalModeCoerce => quote! { EvaluationMode::Coerce },
494-
})
495-
.collect();
496-
497-
// emit asserts for all statements X all modes
498-
let stmts = test_case_expr(&|stmt: &str| {
499-
// emit one assert statement per evaluation mode
500-
let asserts = modes.iter().map(|mode| {
508+
for mode in eval_mode {
509+
let (prefix, mode) = mode_data(mode);
510+
511+
let test_fn = self.create_test(scope, test_case, Some(prefix), true);
512+
test_fn.line("\n//**** evaluation failure test case(s) ****//");
513+
514+
// emit asserts for all statements
515+
let stmts = test_case_expr(&|stmt: &str| {
516+
// emit PartiQL statement and evaluation mode assert
501517
quote! {
518+
let stmt = #stmt;
502519
fail_eval(stmt, #mode, &env);
503520
}
504521
});
505-
// emit PartiQL statement and evaluation mode asserts
506-
quote! {
507-
let stmt = #stmt;
508-
#(#asserts)*
509-
}
510-
});
511-
512-
let tokens = quote! {
513-
#(#stmts)*
514-
};
515-
test_fn.line(tokens.to_string().replace("\\n", "\n"));
522+
523+
test_fn.line(escape_fn_code(quote! {
524+
#(#stmts)*
525+
}));
526+
}
516527
}
517528
}
518529
}
519-
520-
if ignore_test {
521-
test_fn.attr("ignore = \"not yet implemented\"");
522-
}
523530
}
524531
}
525532

partiql-conformance-test-generator/src/util.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use inflector::Inflector;
2+
use quote::__private::TokenStream;
23

34
pub trait Escaper {
45
/// Escapes a string intended to be used in a file path
@@ -51,6 +52,11 @@ impl Escaper for String {
5152
}
5253
}
5354

55+
#[inline]
56+
pub fn escape_fn_code(ts: TokenStream) -> String {
57+
ts.to_string().replace("\\n", "\n")
58+
}
59+
5460
#[cfg(test)]
5561
mod test {
5662
use crate::util::Escaper;

0 commit comments

Comments
 (0)