mirror of
https://github.com/roc-lang/roc.git
synced 2025-08-03 19:58:18 +00:00
Add a Malformed trait, and assert that 'passing' tests don't produce a malformed result
This commit is contained in:
parent
c60dcd763d
commit
a1cd114198
26 changed files with 506 additions and 64 deletions
|
@ -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 {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
my_list = [
|
||||
myList = [
|
||||
0,
|
||||
[
|
||||
a,
|
||||
|
|
|
@ -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",
|
||||
),
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
my_list = [
|
||||
myList = [
|
||||
0,
|
||||
[
|
||||
a,
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
my_list = [
|
||||
myList = [
|
||||
0,
|
||||
1,
|
||||
]
|
||||
|
|
|
@ -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",
|
||||
),
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
my_list = [
|
||||
myList = [
|
||||
0,
|
||||
1
|
||||
]
|
||||
|
|
|
@ -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",
|
||||
),
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
my_list = [
|
||||
myList = [
|
||||
0,
|
||||
1,
|
||||
]
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue