Skip to content

Commit e988715

Browse files
authored
Compare TLAPM AST with expected AST in syntax tests (#229)
Translate TLAPM AST to normalized S-expression form Signed-off-by: Andrew Helwer <[email protected]>
1 parent 386cb32 commit e988715

30 files changed

+2673
-603
lines changed

dune-project

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
ocaml
4444
dune-site
4545
dune-build-info
46+
sexp_diff
4647
sexplib
4748
cmdliner
4849
camlzip

src/tlapm_lib.ml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -653,7 +653,7 @@ let modctx_of_string ~(content : string) ~(filename : string) ~loader_paths ~pre
653653
| None, None -> Error (None, Printexc.to_string e))
654654

655655
let module_of_string module_str =
656-
let hparse = Tla_parser.P.use M_parser.parse in
656+
let hparse = Tla_parser.P.use Module.Parser.parse in
657657
let (flex, _) = Alexer.lex_string module_str in
658658
Tla_parser.P.run hparse ~init:Tla_parser.init ~source:flex
659659

src/tlapm_lib.mli

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ val modctx_of_string :
2525
from a specified string, assume it is located in the
2626
specified path. *)
2727

28-
val module_of_string : string -> M_t.mule option
28+
val module_of_string : string -> Module.T.mule option
2929
(** Parse the specified string as a module. No dependencies
3030
are considered, nor proof obligations are elaborated. *)
3131

test/parser/dune

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
(test
22
(name parser_tests)
33
(modes exe)
4-
(libraries tlapm_lib ounit2 sexplib)
4+
(libraries tlapm_lib ounit2 sexplib sexp_diff)
55
(deps (glob_files_rec syntax_corpus/*))
66
(preprocess (pps ppx_deriving.show))
77
)

test/parser/parser_tests.ml

Lines changed: 100 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -1,93 +1,28 @@
1-
(** This test runs a battery of TLA+ syntax fragments against TLAPM's syntax
2-
parser. In the future it will check the actual parse tree emitted by
3-
TLAPM, but for now it just checks whether TLAPM parses without error all
4-
the syntax it is expected to parse. Tests sourced from:
1+
(** This file runs a battery of TLA+ syntax fragments against TLAPM's syntax
2+
parser. It then takes the resulting parse tree and translates it into a
3+
normalized S-expression form to compare with the associated expected AST.
4+
Tests are sourced from the standardized syntax test corpus at:
55
https://github.com/tlaplus/rfcs/tree/2a772d9dd11acec5d7dedf30abfab91a49de48b8/language_standard/tests/tlaplus_syntax
66
*)
77

8+
open Tlapm_lib;;
9+
810
open Syntax_corpus_file_parser;;
9-
open OUnit2;;
1011

1112
(** Calls TLAPM's parser with the given input. Catches all exceptions and
1213
treats them as parse failures.
1314
@param input The TLA+ fragment to parse.
1415
@return None if parse failure, syntax tree root if successful.
1516
*)
16-
let parse (input : string) : Tlapm_lib__M_t.mule option =
17-
try Tlapm_lib.module_of_string input
17+
let parse (input : string) : Module.T.mule option =
18+
try module_of_string input
1819
with _ -> None
1920

20-
(** Datatype summarizing a run of all the syntax tests. *)
21-
type test_run_summary = {
22-
total : int;
23-
succeeded : int;
24-
failed : int;
25-
skipped : int;
26-
failures : syntax_test_info list;
27-
} [@@deriving show]
28-
29-
(** A blank test summary. *)
30-
let test_summary_init = {
31-
total = 0;
32-
succeeded = 0;
33-
failed = 0;
34-
skipped = 0;
35-
failures = [];
36-
}
37-
38-
(** Runs a given syntax test by determining its type then sending the input
39-
into the TLAPM parser.
40-
@param expect_failure Whether this test should fail due to a TLAPM bug.
41-
@param test Information about the test itself.
42-
@return Whether the test succeeded.
43-
*)
44-
let run_test (test : syntax_test) : bool =
45-
match test.test with
46-
| Error_test input -> parse input |> Option.is_none
47-
| Expected_test (input, _) -> parse input |> Option.is_some
48-
49-
(** Controls run of a given syntax test. Checks whether test should be
50-
skipped and whether it is expected to fail, then runs test and returns
51-
summary.
52-
@param expect_failure Whether this test should fail due to a TLAPM bug.
53-
@param acc Accumulation variable for test summarization.
54-
@param test Information about the test itself.
55-
@return Test run summary.
56-
*)
57-
let control_test_run
58-
(expect_failure : syntax_test -> bool)
59-
(acc : test_run_summary)
60-
(test : syntax_test)
61-
: test_run_summary =
62-
let acc = {acc with total = acc.total + 1} in
63-
if test.skip then {acc with skipped = acc.skipped + 1} else
64-
if run_test test = expect_failure test
65-
then {acc with failed = acc.failed + 1; failures = test.info :: acc.failures}
66-
else {acc with succeeded = acc.succeeded + 1}
67-
68-
(** Given a path to a directory containing a corpus of syntax tests, get all
69-
the tests encoded in those files, filter them as appropriate, then run
70-
them all and collect the results.
71-
@param path Path to the directory containing the corpus of syntax tests.
72-
@param expect_failure Whether a test should fail due to a TLAPM bug.
73-
@param filter_predicate Whether to actually execute a test.
74-
@return Accumulated summary of all test executions.
75-
*)
76-
let run_test_corpus
77-
(path : string)
78-
(expect_failure : syntax_test -> bool)
79-
(filter_pred : syntax_test -> bool)
80-
: test_run_summary =
81-
path
82-
|> get_all_tests_under
83-
|> List.filter filter_pred
84-
|> List.fold_left (control_test_run expect_failure) test_summary_init
85-
8621
(** Names of tests that are known to fail due to TLAPM parser bugs.
8722
@param test Information about the test.
8823
@return Whether the test is expected to fail.
8924
*)
90-
let expect_failure (test : syntax_test) : bool =
25+
let expect_parse_failure (test : syntax_test) : bool =
9126
List.mem test.info.name [
9227

9328
(* https://github.com/tlaplus/tlapm/issues/54#issuecomment-2435515180 *)
@@ -142,24 +77,97 @@ let expect_failure (test : syntax_test) : bool =
14277
"Nonfix Double Exclamation Operator (GH TSTLA #GH97, GH tlaplus/tlaplus #884)";
14378
]
14479

145-
(** Filter predicate to control which tests to run.
146-
@param name Optional; a test name to filter on.
147-
@return Predicate matching all tests or tests with given name.
80+
(** Names of tests that are unable to match the expected output tree, but not
81+
because of a bug; instead, the TLAPM syntax tree doesn't contain the
82+
(usually extraneous) necessary information to fully populate the output
83+
tree with the expected children.
84+
@param test Information about the test.
85+
@return Whether the test should skip the tree comparison phase.
86+
*)
87+
let should_skip_tree_comparison (test : syntax_test) : bool =
88+
List.mem test.info.name [
89+
(* In TLAPM's ASSUME/PROVE parsing, NEW identifiers with unspecified
90+
level are by default Constant instead of Unknown *)
91+
"Assume/Prove With New Identifier of Unspecified Level";
92+
93+
(* Jlist terminated by single line comment omitted in TLAPM AST *)
94+
"Keyword-Unit-Terminated Conjlist";
95+
"Keyword-Unit-Terminated Disjlist";
96+
97+
(* Unnecessary parentheses omitted in TLAPM AST *)
98+
"Nested Parentheses";
99+
100+
(* TLAPM AST does not distinguish between nonfix and infix ops *)
101+
"Lexically-Conflicting Nonfix Operators";
102+
"Minus and Negative";
103+
"Nonfix Minus (GH tlaplus/tlaplus #GH884)";
104+
"Nonfix Prefix Operators";
105+
"Nonfix Infix Operators";
106+
"Nonfix Postfix Operators";
107+
108+
(* TLAPM uses function literals for function definitions *)
109+
(* See: https://github.com/tlaplus/tlapm/issues/237 *)
110+
"Function Literal";
111+
112+
(* TLAPM makes multi-parameter EXCEPT update statements into tuples *)
113+
"Record Update with Multiple Parameters";
114+
"Record Update with Tuple and Non-Tuple Parameters";
115+
116+
(* TLAPM does not distinguish between <=> and \equiv *)
117+
"IFF Disambiguation"
118+
]
119+
120+
(** Names of tests that are expected to fail the tree comparison phase due to
121+
bugs in TLAPM's syntax parser.
122+
@param test Information about the test.
123+
@return Whether the test is expected to fail the tree comparison phase.
148124
*)
149-
let should_run ?name test =
150-
match name with
151-
| Some name -> String.equal test.info.name name
152-
| None -> true
125+
let expect_tree_comparison_failure (test : syntax_test) : bool =
126+
List.mem test.info.name [
127+
(* TLAPM appears to simply return an empty set here? *)
128+
(* https://github.com/tlaplus/tlapm/issues/235 *)
129+
"Mistaken Set Filter Test";
130+
"Mistaken Set Filter Tuples Test";
131+
]
132+
133+
open OUnit2;;
153134

154-
(** The top-level test; runs all syntax tests, prints summary, then fails
155-
with an assertion if any tests failed.
135+
(** Gathers all syntax test files, parses them, then runs the cases they
136+
contain as tests against TLAPM's syntax parser, skipping or expecting
137+
failure as appropriate.
156138
*)
157-
let () =
158-
let test_results =
159-
run_test_corpus
160-
"syntax_corpus"
161-
expect_failure
162-
(should_run (*~name:"Proof Containing Jlist"*))
163-
in
164-
print_endline (show_test_run_summary test_results);
165-
assert_equal 0 test_results.failed;
139+
let tests = "Standardized syntax test corpus" >::: (
140+
get_all_tests_under "syntax_corpus"
141+
|> List.map (fun test ->
142+
Format.sprintf "[%s] %s" test.info.path test.info.name >::
143+
(fun _ ->
144+
skip_if test.skip "Test has skip attribute";
145+
match test.test with
146+
| Error_test input -> (
147+
match parse input with
148+
| None -> assert_bool "Expected error test to fail" (not (expect_parse_failure test))
149+
| Some _ -> assert_bool "Expected parse failure" (expect_parse_failure test)
150+
)
151+
| Expected_test (input, expected) -> (
152+
match parse input with
153+
| None -> assert_bool "Expected parse success" (expect_parse_failure test)
154+
| Some tlapm_output ->
155+
skip_if (should_skip_tree_comparison test) "Skipping parse tree comparison";
156+
let open Translate_syntax_tree in
157+
let open Sexplib in
158+
let actual = tlapm_output |> translate_tla_source_file |> ts_node_to_sexpr in
159+
if Sexp.equal expected actual
160+
then assert_bool "Expected parse test to fail" (not (expect_tree_comparison_failure test))
161+
else
162+
let open Sexp_diff in
163+
let diff = Algo.diff ~original:expected ~updated:actual () in
164+
let options = Display.Display_options.(create Layout.Single_column) in
165+
let text = Display.display_with_ansi_colors options diff in
166+
assert_bool text (expect_tree_comparison_failure test)
167+
)
168+
)
169+
)
170+
)
171+
172+
(** The OUnit2 test entrypoint. *)
173+
let () = run_test_tt_main tests

0 commit comments

Comments
 (0)