Add a Malformed trait, and assert that 'passing' tests don't produce a malformed result

This commit is contained in:
Joshua Warner 2023-01-25 20:57:12 -08:00
parent c60dcd763d
commit a1cd114198
No known key found for this signature in database
GPG key ID: 89AD497003F93FDD
26 changed files with 506 additions and 64 deletions

View file

@ -1,7 +1,7 @@
use bumpalo::Bump;
use roc_fmt::{annotation::Formattable, module::fmt_module};
use roc_parse::{
ast::{Defs, Expr, Module},
ast::{Defs, Expr, Malformed, Module},
module::module_defs,
parser::{Parser, SyntaxError},
state::State,
@ -104,6 +104,20 @@ impl<'a> Output<'a> {
}
}
impl<'a> Malformed for Output<'a> {
fn is_malformed(&self) -> bool {
match self {
Output::Header(header) => header.is_malformed(),
Output::ModuleDefs(defs) => defs.is_malformed(),
Output::Expr(expr) => expr.is_malformed(),
Output::Full {
header,
module_defs,
} => header.is_malformed() || module_defs.is_malformed(),
}
}
}
impl<'a> RemoveSpaces<'a> for Output<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
match self {

View file

@ -4,7 +4,7 @@ Defs(
Index(2147483648),
],
regions: [
@0-58,
@0-57,
],
space_before: [
Slice(start = 0, length = 0),
@ -16,16 +16,13 @@ Defs(
type_defs: [],
value_defs: [
Body(
@0-7 MalformedIdent(
"my_list",
Underscore(
@3,
),
@0-6 Identifier(
"myList",
),
@10-58 List(
@9-57 List(
Collection {
items: [
@16-17 SpaceBefore(
@15-16 SpaceBefore(
Num(
"0",
),
@ -33,11 +30,11 @@ Defs(
Newline,
],
),
@23-48 SpaceBefore(
@22-47 SpaceBefore(
List(
Collection {
items: [
@33-34 SpaceBefore(
@32-33 SpaceBefore(
Var {
module_name: "",
ident: "a",
@ -46,7 +43,7 @@ Defs(
Newline,
],
),
@44-45 SpaceBefore(
@43-44 SpaceBefore(
Var {
module_name: "",
ident: "b",
@ -65,7 +62,7 @@ Defs(
Newline,
],
),
@54-55 SpaceBefore(
@53-54 SpaceBefore(
Num(
"1",
),
@ -82,7 +79,7 @@ Defs(
),
],
},
@59-61 SpaceBefore(
@58-60 SpaceBefore(
Num(
"42",
),

View file

@ -1,4 +1,4 @@
my_list = [
myList = [
0,
[
a,

View file

@ -4,7 +4,7 @@ Defs(
Index(2147483648),
],
regions: [
@0-26,
@0-25,
],
space_before: [
Slice(start = 0, length = 0),
@ -16,15 +16,12 @@ Defs(
type_defs: [],
value_defs: [
Body(
@0-7 MalformedIdent(
"my_list",
Underscore(
@3,
),
@0-6 Identifier(
"myList",
),
@10-26 List(
@9-25 List(
[
@16-17 SpaceBefore(
@15-16 SpaceBefore(
Num(
"0",
),
@ -32,7 +29,7 @@ Defs(
Newline,
],
),
@23-24 SpaceBefore(
@22-23 SpaceBefore(
SpaceAfter(
Num(
"1",
@ -50,7 +47,7 @@ Defs(
),
],
},
@27-29 SpaceBefore(
@26-28 SpaceBefore(
Num(
"42",
),

View file

@ -4,7 +4,7 @@ Defs(
Index(2147483648),
],
regions: [
@0-27,
@0-26,
],
space_before: [
Slice(start = 0, length = 0),
@ -16,16 +16,13 @@ Defs(
type_defs: [],
value_defs: [
Body(
@0-7 MalformedIdent(
"my_list",
Underscore(
@3,
),
@0-6 Identifier(
"myList",
),
@10-27 List(
@9-26 List(
Collection {
items: [
@16-17 SpaceBefore(
@15-16 SpaceBefore(
Num(
"0",
),
@ -33,7 +30,7 @@ Defs(
Newline,
],
),
@23-24 SpaceBefore(
@22-23 SpaceBefore(
Num(
"1",
),
@ -50,7 +47,7 @@ Defs(
),
],
},
@28-30 SpaceBefore(
@27-29 SpaceBefore(
Num(
"42",
),

View file

@ -10,9 +10,9 @@ mod test_snapshots {
use bumpalo::collections::vec::Vec;
use bumpalo::{self, Bump};
use roc_parse::ast::Expr::{self, *};
use roc_parse::ast::StrLiteral::*;
use roc_parse::ast::StrSegment::*;
use roc_parse::ast::{self, EscapedChar};
use roc_parse::ast::{Malformed, StrLiteral::*};
use roc_parse::parser::SyntaxError;
use roc_parse::test_helpers::parse_expr_with;
use roc_region::all::{Loc, Region};
@ -35,25 +35,45 @@ mod test_snapshots {
};
}
macro_rules! should_pass {
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum TestExpectation {
Pass, // The test parses successfully and there are no Malformed nodes
Fail, // The test gives a parse error
Malformed, // The test parses successfully but there are Malformed nodes
}
impl TestExpectation {
fn to_dir_name(self) -> &'static str {
match self {
TestExpectation::Pass => "pass",
TestExpectation::Fail => "fail",
TestExpectation::Malformed => "malformed",
}
}
}
macro_rules! test_expectation {
(pass) => {
true
TestExpectation::Pass
};
(fail) => {
false
TestExpectation::Fail
};
(malformed) => {
TestExpectation::Malformed
};
}
macro_rules! snapshot_tests {
(
$($pass_or_fail:ident / $test_name:ident . $kind:ident),*
$($pass_or_fail_or_malformed:ident / $test_name:ident . $kind:ident),*
$(,)?
) => {
#[test]
fn no_extra_snapshot_test_files() {
let tests = &[
$(concat!(
stringify!($pass_or_fail),
stringify!($pass_or_fail_or_malformed),
"/",
stringify!($test_name),
".",
@ -70,7 +90,7 @@ mod test_snapshots {
let pass_or_fail_names = list(&base);
let mut extra_test_files = std::collections::HashSet::new();
for res in pass_or_fail_names {
assert!(res == "pass" || res == "fail", "a pass or fail filename was neither \"pass\" nor \"fail\", but rather: {:?}", res);
assert!(res == "pass" || res == "fail" || res == "malformed", "expected only pass/fail/malformed dirs, but I see: {:?}", res);
let res_dir = base.join(&res);
for file in list(&res_dir) {
let test = if let Some(test) = file.strip_suffix(".formatted.roc") {
@ -150,7 +170,7 @@ mod test_snapshots {
#[test]
fn $test_name() {
snapshot_test(
should_pass!($pass_or_fail),
test_expectation!($pass_or_fail_or_malformed),
stringify!($test_name),
stringify!($kind),
|input| snapshot_input!($kind => input));
@ -227,6 +247,11 @@ mod test_snapshots {
fail/when_over_indented_int.expr,
fail/when_over_indented_underscore.expr,
fail/wild_case_arrow.expr,
malformed/bad_opaque_ref.expr,
malformed/malformed_ident_due_to_underscore.expr,
malformed/malformed_pattern_field_access.expr, // See https://github.com/roc-lang/roc/issues/399
malformed/malformed_pattern_module_name.expr, // See https://github.com/roc-lang/roc/issues/399
malformed/underscore_expr_in_def.expr,
pass/ability_demand_signature_is_multiline.expr,
pass/ability_multi_line.expr,
pass/ability_single_line.expr,
@ -244,7 +269,6 @@ mod test_snapshots {
pass/apply_two_args.expr,
pass/apply_unary_negation.expr,
pass/apply_unary_not.expr,
pass/bad_opaque_ref.expr,
pass/basic_apply.expr,
pass/basic_docs.expr,
pass/basic_field.expr,
@ -301,9 +325,6 @@ mod test_snapshots {
pass/list_patterns.expr,
pass/lowest_float.expr,
pass/lowest_int.expr,
pass/malformed_ident_due_to_underscore.expr,
pass/malformed_pattern_field_access.expr, // See https://github.com/roc-lang/roc/issues/399
pass/malformed_pattern_module_name.expr, // See https://github.com/roc-lang/roc/issues/399
pass/minimal_app_header.header,
pass/minus_twelve_minus_five.expr,
pass/mixed_docs.expr,
@ -324,8 +345,8 @@ mod test_snapshots {
pass/negative_float.expr,
pass/negative_in_apply_def.expr,
pass/negative_int.expr,
pass/nested_def_annotation.moduledefs,
pass/nested_backpassing_no_newline_before.expr,
pass/nested_def_annotation.moduledefs,
pass/nested_def_without_newline.expr,
pass/nested_if.expr,
pass/nested_module.header,
@ -384,7 +405,7 @@ mod test_snapshots {
pass/positive_int.expr,
pass/provides_type.header,
pass/qualified_field.expr,
pass/qualified_tag.expr,
malformed/qualified_tag.expr,
pass/qualified_var.expr,
pass/record_access_after_tuple.expr,
pass/record_destructure_def.expr,
@ -423,7 +444,6 @@ mod test_snapshots {
pass/unary_not.expr,
pass/unary_not_with_parens.expr,
pass/underscore_backpassing.expr,
pass/underscore_expr_in_def.expr,
pass/underscore_in_assignment_pattern.expr,
pass/value_def_confusion.expr,
pass/var_else.expr,
@ -501,13 +521,13 @@ mod test_snapshots {
}
}
fn snapshot_test<F>(should_pass: bool, name: &str, ty: &str, func: F)
fn snapshot_test<F>(expect: TestExpectation, name: &str, ty: &str, func: F)
where
F: for<'a> Fn(&'a str) -> Input<'a>,
{
let mut parent = std::path::PathBuf::from("tests");
parent.push("snapshots");
parent.push(if should_pass { "pass" } else { "fail" });
parent.push(expect.to_dir_name());
let input_path = parent.join(&format!("{}.{}.roc", name, ty));
let result_path = parent.join(&format!("{}.{}.result-ast", name, ty));
let formatted_path = parent.join(&format!("{}.{}.formatted.roc", name, ty));
@ -523,21 +543,27 @@ mod test_snapshots {
let arena = Bump::new();
let result = match input.parse_in(&arena) {
Ok(ast) => Ok(ast.debug_format_inner()),
Ok(ast) => {
if expect == TestExpectation::Pass {
assert!(!ast.is_malformed());
}
Ok(ast.debug_format_inner())
}
Err(err) => Err(format!("{:?}", err)),
};
let actual_result = if should_pass {
result.expect("The source code for this test did not successfully parse!")
} else {
result.expect_err(
let actual_result =
if expect == TestExpectation::Pass || expect == TestExpectation::Malformed {
result.expect("The source code for this test did not successfully parse!")
} else {
result.expect_err(
"The source code for this test successfully parsed, but it was not expected to!",
)
};
};
compare_snapshots(&result_path, Some(&actual_result));
if should_pass {
if expect == TestExpectation::Pass || expect == TestExpectation::Malformed {
input.check_invariants(check_saved_formatting(input.as_str(), formatted_path), true);
}
}