From f1da09b54309686b771db1a926dd9de60ba150a0 Mon Sep 17 00:00:00 2001 From: Hou Seng Wong Date: Fri, 21 Jan 2022 18:37:02 -0800 Subject: [PATCH 01/11] port half of validationTest --- .../compiler/LinguaFrancaValidationTest.java | 1442 +++++++++++++++++ 1 file changed, 1442 insertions(+) create mode 100644 org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaValidationTest.java diff --git a/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaValidationTest.java b/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaValidationTest.java new file mode 100644 index 0000000000..0e09a1cee2 --- /dev/null +++ b/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaValidationTest.java @@ -0,0 +1,1442 @@ +/* Scoping unit tests. */ + +/************* +Copyright (c) 2019, The University of California at Berkeley. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +***************/ +package org.lflang.tests.compiler; + +import com.google.inject.Inject; +import java.util.List; +import org.eclipse.xtext.testing.InjectWith; +import org.eclipse.xtext.testing.extensions.InjectionExtension; +import org.eclipse.xtext.testing.util.ParseHelper; +import org.eclipse.xtext.testing.validation.ValidationTestHelper; +import org.lflang.Target; +import org.lflang.TargetProperty; +import org.lflang.TargetProperty.DictionaryType; +import org.lflang.TargetProperty.PrimitiveType; +import org.lflang.TargetProperty.TargetPropertyType; +import org.lflang.TimeValue; +import org.lflang.lf.LfPackage; +import org.lflang.lf.Model; +import org.lflang.lf.Visibility; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +import static extension org.lflang.ASTUtils.*; +import org.lflang.TargetProperty.UnionType; +import org.lflang.TargetProperty.ArrayType; +import org.lflang.tests.LFInjectorProvider; + +@ExtendWith(InjectionExtension.class) +@InjectWith(LFInjectorProvider.class) + + +/** + * Collection of unit tests to ensure validation is done correctly. + * + * @author{Edward A. Lee } + * @author{Marten Lohstroh } + * @author{Matt Weber } + * @author(Christian Menard } + */ +public class LinguaFrancaValidationTest { + @Inject + ParseHelper parser; + + @Inject + ValidationTestHelper validator; + + /** + * Helper function to parse a Lingua Franca program and expect no errors. + * @return A model representing the parsed string. + */ + private Model parseWithoutError(String s) { + Model model = parser.parse(s); + Assertions.assertNotNull(model); + Assertions.assertTrue(model.eResource.errors.isEmpty, + "Encountered unexpected error while parsing: " + + model.eResource.errors); + return model; + } + + /** + * Helper function to parse a Lingua Franca program and expect errors. + * @return A model representing the parsed string. + */ + private Model parseWithError(String s) { + Model model = parser.parse(s); + Assertions.assertNotNull(model); + Assertions.assertFalse(model.eResource.errors.isEmpty); + return model; + } + + + /** + * Ensure that duplicate identifiers for actions reported. + */ + @Test + public void duplicateVariable() { +// Java 17: +// String testCase = """ +// target TypeScript; +// main reactor Foo { +// logical action bar; +// physical action bar; +// } +// """ +// Java 11: + String testCase = String.join(System.getProperty("line.separator"), + "target TypeScript;", + "main reactor Foo {", + " logical action bar;", + " physical action bar;", + "}"); + parseWithoutError(testCase).assertError(LfPackage::eINSTANCE.action, null, + "Duplicate Variable 'bar' in Reactor 'Foo'"); + } + + + /** + * Check that reactors in C++ cannot be named preamble + */ + @Test + public void disallowReactorCalledPreamble() { +// Java 17: +// Model model_no_errors = """ +// target Cpp; +// main reactor { +// } +// """ +// Java 11: + Model model_no_errors = parser.parse(String.join( + System.getProperty("line.separator"), + "target Cpp;", + "main reactor {", + "}" + )); + +// Java 17: +// Model model_error_1 = """ +// target Cpp; +// main reactor Preamble { +// } +// """ +// Java 11: + Model model_error_1 = parser.parse(String.join( + System.getProperty("line.separator"), + "target Cpp;", + "main reactor Preamble {", + "}" + )); + +// Java 17: +// Model model_error_2 = """ +// target Cpp; +// reactor Preamble { +// } +// main reactor Main { +// } +// """ +// Java 11: + Model model_error_2 = parser.parse(String.join( + System.getProperty("line.separator"), + "target Cpp;", + "reactor Preamble {", + "}", + "main reactor Main {", + "}" + )); + + Assertions.assertNotNull(model_no_errors); + Assertions.assertNotNull(model_error_1); + Assertions.assertNotNull(model_error_2); + Assertions.assertTrue(model_no_errors.eResource.errors.isEmpty, + "Encountered unexpected error while parsing: " + model_no_errors.eResource.errors); + Assertions.assertTrue(model_error_1.eResource.errors.isEmpty, + "Encountered unexpected error while parsing: " + model_error_1.eResource.errors); + Assertions.assertTrue(model_error_2.eResource.errors.isEmpty, + "Encountered unexpected error while parsing: " + model_error_2.eResource.errors); + + model_no_errors.assertNoIssues(); + model_error_1.assertError(LfPackage::eINSTANCE.reactor, null, + "Reactor cannot be named 'Preamble'"); + model_error_2.assertError(LfPackage::eINSTANCE.reactor, null, + "Reactor cannot be named 'Preamble'"); + } + + + /** + * Ensure that "__" is not allowed at the start of an input name. + */ + @Test + public void disallowUnderscoreInputs() { +// Java 17: +// String testCase = """ +// target TypeScript; +// main reactor { +// input __bar; +// } +// """ +// Java 11: + String testCase = String.join(System.getProperty("line.separator"), + "target TypeScript;", + "main reactor {", + " input __bar;", + "}"); + + parseWithoutError(testCase).assertError(LfPackage::eINSTANCE.input, null, + "Names of objects (inputs, outputs, actions, timers, parameters, state, reactor definitions, and reactor instantiation) may not start with \"__\": __bar"); + } + + @Test + public void disallowMainWithDifferentNameThanFile() { +// Java 17: +// String testCase = """ +// target C; +// main reactor Foo {} +// """ +// Java 11: + String testCase = String.join(System.getProperty("line.separator"), + "target C;", + "main reactor Foo {}" + ); + + parseWithoutError(testCase).assertError(LfPackage::eINSTANCE.reactor, null, + "Name of main reactor must match the file name (or be omitted)"); + } + + + /** + * Ensure that "__" is not allowed at the start of an output name. + */ + @Test + public void disallowUnderscoreOutputs() { +// Java 17: +// String testCase = """ +// target TypeScript; +// main reactor Foo { +// output __bar; +// } +// """ +// Java 11: + String testCase = String.join(System.getProperty("line.separator"), + "target TypeScript;", + "main reactor Foo {", + " output __bar;", + "}"); + + parseWithoutError(testCase).assertError(LfPackage::eINSTANCE.output, null, + "Names of objects (inputs, outputs, actions, timers, parameters, state, reactor definitions, and reactor instantiation) may not start with \"__\": __bar"); + } + + /** + * Ensure that "__" is not allowed at the start of an action name. + */ + @Test + public void disallowUnderscoreActions() { +// Java 17: +// String testCase = """ +// target TypeScript; +// main reactor Foo { +// logical action __bar; +// } +// """ +// Java 11: + String testCase = String.join(System.getProperty("line.separator"), + "target TypeScript;", + "main reactor Foo {", + " logical action __bar;", + "}"); + parseWithoutError(testCase).assertError(LfPackage::eINSTANCE.action, null, + "Names of objects (inputs, outputs, actions, timers, parameters, state, reactor definitions, and reactor instantiation) may not start with \"__\": __bar"); + } + + /** + * Ensure that "__" is not allowed at the start of a timer name. + */ + @Test + public void disallowUnderscoreTimers() { +// Java 17: +// String testCase = """ +// target TypeScript; +// main reactor Foo { +// timer __bar(0); +// } +// """ +// Java 11: + String testCase = String.join(System.getProperty("line.separator"), + "target TypeScript;", + "main reactor Foo {", + " timer __bar(0);", + "}"); + parseWithoutError(testCase).assertError(LfPackage::eINSTANCE.timer, null, + "Names of objects (inputs, outputs, actions, timers, parameters, state, reactor definitions, and reactor instantiation) may not start with \"__\": __bar"); + } + + /** + * Ensure that "__" is not allowed at the start of a parameter name. + */ + @Test + public void disallowUnderscoreParameters() { +// Java 17: +// String testCase = """ +// target TypeScript; +// main reactor Foo(__bar) { +// } +// """ +// Java 11: + String testCase = String.join(System.getProperty("line.separator"), + "target TypeScript;", + "main reactor Foo(__bar) {", + "}"); + parseWithoutError(testCase).assertError(LfPackage::eINSTANCE.parameter, null, + "Names of objects (inputs, outputs, actions, timers, parameters, state, reactor definitions, and reactor instantiation) may not start with \"__\": __bar"); + } + + /** + * Ensure that "__" is not allowed at the start of an state name. + */ + @Test + public void disallowUnderscoreStates() { +// Java 17: +// String testCase = """ +// target TypeScript; +// main reactor Foo { +// state __bar; +// } +// """ +// Java 11: + String testCase = String.join(System.getProperty("line.separator"), + "target TypeScript;", + "main reactor Foo {", + " state __bar;", + "}"); + parseWithoutError(testCase).assertError(LfPackage::eINSTANCE.stateVar, null, + "Names of objects (inputs, outputs, actions, timers, parameters, state, reactor definitions, and reactor instantiation) may not start with \"__\": __bar"); + } + + /** + * Ensure that "__" is not allowed at the start of a reactor definition name. + */ + @Test + public void disallowUnderscoreReactorDef() { +// Java 17: +// String testCase = """ +// target TypeScript; +// main reactor __Foo { +// } +// """ +// Java 11: + String testCase = String.join(System.getProperty("line.separator"), + "target TypeScript;", + "main reactor __Foo {", + "}"); + parseWithoutError(testCase).assertError(LfPackage::eINSTANCE.reactor, null, + "Names of objects (inputs, outputs, actions, timers, parameters, state, reactor definitions, and reactor instantiation) may not start with \"__\": __Foo"); + } + + /** + * Ensure that "__" is not allowed at the start of a reactor instantiation name. + */ + @Test + public void disallowUnderscoreReactorInstantiation() { +// Java 17: +// String testCase = """ +// target TypeScript; +// reactor Foo { +// } +// main reactor Bar { +// __x = new Foo(); +// } +// """ +// Java 11: + String testCase = String.join(System.getProperty("line.separator"), + "target TypeScript;", + "reactor Foo {", + "}", + "main reactor Bar {", + " __x = new Foo();", + "}"); + parseWithoutError(testCase).assertError(LfPackage::eINSTANCE.instantiation, null, + "Names of objects (inputs, outputs, actions, timers, parameters, state, reactor definitions, and reactor instantiation) may not start with \"__\": __x"); + } + + /** + * Disallow connection to port that is effect of reaction. + */ + @Test + public void connectionToEffectPort() { +// Java 17: +// String testCase = """ +// target C; +// reactor Foo { +// output out:int; +// } +// main reactor Bar { +// output out:int; +// x = new Foo(); +// x.out -> out; +// reaction(startup) -> out {= +// =} +// } +// """ +// Java 11: + String testCase = String.join(System.getProperty("line.separator"), + "target C;", + "reactor Foo {", + " output out:int;", + "}", + "main reactor Bar {", + " output out:int;", + " x = new Foo();", + " x.out -> out;", + " reaction(startup) -> out {=", + " =}", + "}"); + parseWithoutError(testCase).assertError(LfPackage::eINSTANCE.connection, null, + "Cannot connect: Port named 'out' is already effect of a reaction."); + } + + /** + * Disallow connection to port that is effect of reaction. + */ + @Test + public void connectionToEffectPort2() { +// Java 17: +// String testCase = """ +// target C; +// reactor Foo { +// input inp:int; +// output out:int; +// } +// main reactor { +// output out:int; +// x = new Foo(); +// y = new Foo(); +// +// y.out -> x.inp; +// reaction(startup) -> x.inp {= +// =} +// } +// """ +// Java 11: + String testCase = String.join(System.getProperty("line.separator"), + "target C;", + "reactor Foo {", + " input inp:int;", + " output out:int;", + "}", + "main reactor {", + " output out:int;", + " x = new Foo();" + " y = new Foo();", + "", + " y.out -> x.inp;", + " reaction(startup) -> x.inp {=", + " =}", + "}"); + parseWithoutError(testCase).assertError(LfPackage::eINSTANCE.connection, null, + "Cannot connect: Port named 'inp' is already effect of a reaction."); + } + + /** + * Allow connection to the port of a contained reactor if another port with same name is effect of a reaction. + */ + @Test + public void connectionToEffectPort3() { +// Java 17: +// String testCase = """ +// target C; +// +// reactor Foo { +// input in:int; +// } +// main reactor { +// input in:int; +// x1 = new Foo(); +// x2 = new Foo(); +// in -> x1.in; +// reaction(startup) -> x2.in {= +// =} +// } +// """ +// Java 11: + String testCase = String.join(System.getProperty("line.separator"), + "target C;", + "", + "reactor Foo {", + " input in:int;", + "}", + "main reactor {", + " input in:int;", + " x1 = new Foo();" + " x2 = new Foo();", + " in -> x1.in;", + " reaction(startup) -> x2.in {=", + " =}", + "}"); + parseWithoutError(testCase).assertNoErrors(); + } + + /** + * Disallow connection to the port of a contained reactor if the same port is effect of a reaction. + */ + @Test + public void connectionToEffectPort4() { +// Java 17: +// String testCase = """ +// target C; + +// reactor Foo { +// input in:int; +// } +// main reactor { +// input in:int; +// x1 = new Foo(); +// in -> x1.in; +// reaction(startup) -> x1.in {= +// =} +// } +// """ +// Java 11: + String testCase = String.join(System.getProperty("line.separator"), + "target C;", + "", + "reactor Foo {", + " input in:int;", + "}", + "main reactor {", + " input in:int;", + " x1 = new Foo();" + " in -> x1.in;", + " reaction(startup) -> x1.in {=", + " =}", + "}"); + parseWithoutError(testCase).assertError(LfPackage::eINSTANCE.connection, null, + "Cannot connect: Port named 'in' is already effect of a reaction."); + } + + /** + * Disallow connection of multiple ports to the same input port. + */ + @Test + public void multipleConnectionsToInputTest() { +// Java 17: +// String testCase = """ +// target C; +// reactor Source { +// output out:int; +// } +// reactor Sink { +// input in:int; +// } +// main reactor { +// input in:int; +// src = new Source(); +// sink = new Sink(); +// in -> sink.in; +// src.out -> sink.in; +// } +// """ +// Java 11: + String testCase = String.join(System.getProperty("line.separator"), + "target C;", + "reactor Source {", + " output out:int;", + "}", + "reactor Sink {", + " input in:int;", + "}" + "main reactor {", + " input in:int;", + " src = new Source();", + " sink = new Sink();", + " in -> sink.in;", + " src.out -> sink.in;", + "}"); + parseWithoutError(testCase).assertError(LfPackage::eINSTANCE.connection, null, + "Cannot connect: Port named 'in' may only appear once on the right side of a connection."); + } + + /** + * Detect cycles in the instantiation graph. + */ + @Test + public void detectInstantiationCycle() { +// Java 17: +// String testCase = """ +// target C; +// reactor Contained { +// x = new Contained(); +// } +// """ +// Java 11: + String testCase = String.join(System.getProperty("line.separator"), + "target C;", + "reactor Contained {", + " x = new Contained();", + "}"); + parseWithoutError(testCase).assertError(LfPackage::eINSTANCE.instantiation, + null, "Instantiation is part of a cycle: Contained"); + } + + + /** + * Detect cycles in the instantiation graph. + */ + @Test + public void detectInstantiationCycle2() { +// Java 17: +// String testCase = """ +// target C; +// reactor Intermediate { +// x = new Contained(); +// } +// +// reactor Contained { +// x = new Intermediate(); +// } +// """ +// Java 11: + Model model = parseWithoutError(String.join( + System.getProperty("line.separator"), + "target C;", + "reactor Intermediate {", + " x = new Contained();", + "}", + "", + "reactor Contained {", + " x = new Intermediate();", + "}" + )); + model.assertError(LfPackage::eINSTANCE.instantiation, + null, "Instantiation is part of a cycle: Intermediate, Contained."); + model.assertError(LfPackage::eINSTANCE.instantiation, + null, "Instantiation is part of a cycle: Intermediate, Contained."); + } + + /** + * Detect causality loop. + */ + @Test + public void detectCausalityLoop() { +// Java 17: +// String testCase = """ +// target C; +// +// reactor X { +// input x:int; +// output y:int; +// reaction(x) -> y {= +// =} +// } +// +// main reactor { +// a = new X() +// b = new X() +// a.y -> b.x +// b.y -> a.x +// } +// """ +// Java 11: + Model model = parseWithoutError(String.join( + System.getProperty("line.separator"), + "target C;", + "", + "reactor X {", + " input x:int", + " output y:int;", + " reaction(x) -> y {=", + " =}", + "}", + "", + "main reactor {", + " a = new X()", + " a = new X()", + " a.y -> b.x", + " b.y -> a.x", + "}" + )); + model.assertError(LfPackage::eINSTANCE.reaction, + null, "Reaction triggers involved in cyclic dependency in reactor X: x."); + model.assertError(LfPackage::eINSTANCE.reaction, + null, "Reaction effects involved in cyclic dependency in reactor X: y."); + + } + + /** + * Let cyclic dependencies be broken by "after" clauses. + */ + @Test + public void afterBreaksCycle() { + parseWithoutError(''' + target C + + reactor X { + input x:int; + output y:int; + reaction(x) -> y {= + =} + } + + main reactor { + a = new X() + b = new X() + a.y -> b.x after 5 msec + b.y -> a.x + } + + ''').assertNoErrors() + + } + + + /** + * Let cyclic dependencies be broken by "after" clauses with zero delay. + */ + @Test + public void afterBreaksCycle2() { + parseWithoutError(''' + target C + + reactor X { + input x:int; + output y:int; + reaction(x) -> y {= + =} + } + + main reactor { + a = new X() + b = new X() + a.y -> b.x after 0 sec + b.y -> a.x + } + + ''').assertNoErrors() + + } + + + /** + * Let cyclic dependencies be broken by "after" clauses with zero delay and no units. + */ + @Test + public void afterBreaksCycle3() { + parseWithoutError(''' + target C + + reactor X { + input x:int; + output y:int; + reaction(x) -> y {= + =} + } + + main reactor { + a = new X() + b = new X() + a.y -> b.x after 0 + b.y -> a.x + } + + ''').assertNoErrors() + + } + + /** + * Detect missing units in "after" clauses with delay greater than zero. + */ + @Test + public void nonzeroAfterMustHaveUnits() { + parseWithoutError(''' + target C + + reactor X { + input x:int; + output y:int; + reaction(x) -> y {= + =} + } + + main reactor { + a = new X() + b = new X() + a.y -> b.x after 1 + } + + ''').assertError(LfPackage::eINSTANCE.time, + null, 'Missing time unit.') + + } + + + + /** + * Report non-zero time value without units. + */ + @Test + public void nonZeroTimeValueWithoutUnits() { + parseWithoutError(''' + target C; + main reactor { + timer t(42, 1 sec); + reaction(t) {= + printf("Hello World.\n"); + =} + } + ''').assertError(LfPackage::eINSTANCE.value, null, "Missing time unit.") + } + + /** + * Report reference to non-time parameter in time argument. + */ + @Test + public void parameterTypeMismatch() { + parseWithoutError(''' + target C; + main reactor (p:int(0)) { + timer t(p, 1 sec); + reaction(t) {= + printf("Hello World.\n"); + =} + } + ''').assertError(LfPackage::eINSTANCE.value, + null, 'Parameter is not of time type') + + } + + /** + * Report inappropriate literal in time argument. + */ + @Test + public void targetCodeInTimeArgument() { + parseWithoutError(''' + target C; + main reactor { + timer t({=foo()=}, 1 sec); + reaction(t) {= + printf("Hello World.\n"); + =} + } + ''').assertError(LfPackage::eINSTANCE.value, + null, 'Invalid time literal') + } + + + /** + * Report overflowing deadline. + */ + @Test + public void overflowingDeadlineC() { + parseWithoutError(''' + target C; + main reactor { + timer t; + reaction(t) {= + printf("Hello World.\n"); + =} deadline (40 hours) {= + =} + } + ''').assertError(LfPackage::eINSTANCE.deadline, null, + "Deadline exceeds the maximum of " + TimeValue.MAX_LONG_DEADLINE + + " nanoseconds.") + } + + + /** + * Report overflowing parameter. + */ + @Test + public void overflowingParameterC() { + parseWithoutError(''' + target C; + main reactor(d:time(40 hours)) { + timer t; + reaction(t) {= + printf("Hello World.\n"); + =} deadline (d) {= + =} + } + ''').assertError(LfPackage::eINSTANCE.parameter, null, + "Time value used to specify a deadline exceeds the maximum of " + + TimeValue.MAX_LONG_DEADLINE + " nanoseconds.") + } + + + /** + * Report overflowing assignment. + */ + @Test + public void overflowingAssignmentC() { + parseWithoutError(''' + target C; + reactor Print(d:time(39 hours)) { + timer t; + reaction(t) {= + printf("Hello World.\n"); + =} deadline (d) {= + =} + } + main reactor { + p = new Print(d=40 hours); + } + ''').assertError(LfPackage::eINSTANCE.assignment, null, + "Time value used to specify a deadline exceeds the maximum of " + + TimeValue.MAX_LONG_DEADLINE + " nanoseconds.") + } + + /** + * Report missing trigger. + */ + @Test + public void missingTrigger() { + parseWithoutError(''' + target C; + reactor X { + reaction() {= + // + =} + } + ''').assertWarning(LfPackage::eINSTANCE.reaction, null, + "Reaction has no trigger.") + } + + /** + * Test warnings and errors for the target dependent preamble visibility qualifiers + */ + @Test + public void testPreambleVisibility() { + for (target : Target.values) { + for (visibility : Visibility.values) { + val model_reactor_scope = parseWithoutError(''' + target «target»; + reactor Foo { + «IF visibility != Visibility.NONE»«visibility» «ENDIF»preamble {==} + } + ''') + + val model_file_scope = parseWithoutError(''' + target «target»; + «IF visibility != Visibility.NONE»«visibility» «ENDIF»preamble {==} + reactor Foo { + } + ''') + + val model_no_preamble = parseWithoutError(''' + target «target»; + reactor Foo { + } + ''') + + model_no_preamble.assertNoIssues + + if (target == Target.CPP) { + if (visibility == Visibility.NONE) { + model_file_scope.assertError(LfPackage::eINSTANCE.preamble, null, + "Preambles for the C++ target need a visibility qualifier (private or public)!") + model_reactor_scope.assertError(LfPackage::eINSTANCE.preamble, null, + "Preambles for the C++ target need a visibility qualifier (private or public)!") + } else { + model_file_scope.assertNoIssues + model_reactor_scope.assertNoIssues + } + } else { + if (visibility == Visibility.NONE) { + model_file_scope.assertNoIssues + model_reactor_scope.assertNoIssues + } else { + model_file_scope.assertWarning(LfPackage::eINSTANCE.preamble, null, + '''The «visibility» qualifier has no meaning for the «target.name» target. It should be removed.''') + model_reactor_scope.assertWarning(LfPackage::eINSTANCE.preamble, null, + '''The «visibility» qualifier has no meaning for the «target.name» target. It should be removed.''') + } + } + } + } + } + + + /** + * Tests for state and parameter declarations, including native lists. + */ + @Test + public void stateAndParameterDeclarationsInC() { + val model = parseWithoutError(''' + target C; + reactor Bar(a(0), // ERROR: type missing + b:int, // ERROR: uninitialized + t:time(42), // ERROR: units missing + x:int(0), + h:time("bla"), // ERROR: not a type + q:time(1 msec, 2 msec), // ERROR: not a list + y:int(t) // ERROR: init using parameter + ) { + state offset:time(42); // ERROR: units missing + state w:time(x); // ERROR: parameter is not a time + state foo:time("bla"); // ERROR: assigned value not a time + timer tick(1); // ERROR: not a time + } + ''') + + model.assertError(LfPackage::eINSTANCE.parameter, null, + "Type declaration missing.") + model.assertError(LfPackage::eINSTANCE.parameter, null, + "Missing time unit.") + model.assertError(LfPackage::eINSTANCE.parameter, null, + "Invalid time literal.") + model.assertError(LfPackage::eINSTANCE.parameter, null, + "Time parameter cannot be initialized using a list.") + model.assertError(LfPackage::eINSTANCE.parameter, null, + "Parameter cannot be initialized using parameter.") + model.assertError(LfPackage::eINSTANCE.stateVar, null, + "Referenced parameter does not denote a time.") + model.assertError(LfPackage::eINSTANCE.stateVar, null, + "Invalid time literal.") + model.assertError(LfPackage::eINSTANCE.parameter, null, + "Uninitialized parameter.") + model.assertError(LfPackage::eINSTANCE.value, null, + "Missing time unit.") + } + + + /** + * Recognize valid IPV4 addresses, report invalid ones. + */ + @Test + public void recognizeIPV4() { + + val correct = #["127.0.0.1", "10.0.0.1", "192.168.1.1", "0.0.0.0", + "192.168.1.1"] + val parseError = #["10002.3.4", "1.2.3.4.5"] + val validationError = #["256.0.0.0", "260.0.0.0"] + + // Correct IP addresses. + correct.forEach [ addr | + parseWithoutError(''' + target C; + reactor Y {} + federated reactor X at foo@«addr»:4242 { + y = new Y() at «addr»:2424; + } + ''') + ] + + // IP addresses that don't parse. + parseError.forEach [ addr | + parseWithError(''' + target C; + reactor Y {} + federated reactor X at foo@«addr»:4242 { + y = new Y() at «addr»:2424; + } + ''') + ] + + // IP addresses that parse but are invalid. + validationError.forEach [ addr | + parseWithoutError(''' + target C; + reactor Y {} + federated reactor X at foo@«addr»:4242 { + y = new Y() at «addr»:2424; + } + ''').assertWarning(LfPackage::eINSTANCE.host, null, + "Invalid IP address.") + ] + } + + /** + * Recognize valid IPV6 addresses, report invalid ones. + */ + @Test + public void recognizeIPV6() { + + val correct = #["1:2:3:4:5:6:7:8", "1:2:3:4:5:6:7::", "1:2:3:4:5:6::8", + "1:2:3:4:5::8", "1:2:3:4::8", "1:2:3::8", "1:2::8", "1::8", "::8", + "::", "1::3:4:5:6:7:8", "1::4:5:6:7:8", "1::5:6:7:8", "1::6:7:8", + "1::7:8", "1::8", "1::", "1:2:3:4:5::7:8", "1:2:3:4::6:7:8", + "1:2:3::5:6:7:8", "1:2::4:5:6:7:8", "1::3:4:5:6:7:8", + "::2:3:4:5:6:7:8", "fe80::7:8", "fe80::7:8%eth0", "fe80::7:8%1", + "::255.255.255.255", "::ffff:255.255.255.255", + "::ffff:0:255.255.255.0", "::ffff:00:255.255.255.0", + "::ffff:000:255.255.255.0", "::ffff:0000:255.255.255.0", + "::ffff:0.0.0.0", "::ffff:1.2.3.4", "::ffff:10.0.0.1", + "1:2:3:4:5:6:77:88", "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", + "2001:db8:3:4::192.0.2.33", "64:ff9b::192.0.2.33", "0:0:0:0:0:0:10.0.0.1"] + + val validationError = #["1:2:3:4:5:6:7:8:9", "1:2:3:4:5:6::7:8", + "1:2:3:4:5:6:7:8:", "::1:2:3:4:5:6:7:8", "1:2:3:4:5:6:7:8::", + "1:2:3:4:5:6:7:88888", "2001:db8:3:4:5::192.0.2.33", + "fe08::7:8interface", "fe08::7:8interface", "fe08::7:8i"] + + val parseError = #["fe08::7:8%", ":1:2:3:4:5:6:7:8"] + + // Correct IP addresses. + correct.forEach [ addr | + parseWithoutError(''' + target C; + reactor Y {} + federated reactor at [foo@«addr»]:4242 { + y = new Y() at [«addr»]:2424; + } + ''').assertNoIssues() + ] + + // IP addresses that don't parse. + parseError.forEach [ addr | + parseWithError(''' + target C; + reactor Y {} + federated reactor at [foo@«addr»]:4242 { + y = new Y() at [«addr»]:2424; + } + ''') + ] + + // IP addresses that parse but are invalid. + validationError.forEach [ addr | + parseWithoutError(''' + target C; + reactor Y {} + federated reactor at [foo@«addr»]:4242 { + y = new Y() at [«addr»]:2424; + } + ''').assertWarning(LfPackage::eINSTANCE.host, null, + "Invalid IP address.") + ] + } + + /** + * Recognize valid host names and fully qualified names, report invalid ones. + */ + @Test + public void recognizeHostNames() { + + val correct = #["localhost"] // FIXME: add more + + val validationError = #["x.y.z"] // FIXME: add more + + val parseError = #["..xyz"] // FIXME: add more + + // Correct names. + correct.forEach [ addr | + parseWithoutError(''' + target C; + reactor Y {} + federated reactor at foo@«addr»:4242 { + y = new Y() at «addr»:2424; + } + ''').assertNoIssues() + ] + + // Names that don't parse. + parseError.forEach [ addr | + parseWithError(''' + target C; + reactor Y {} + federated reactor at foo@«addr»:4242 { + y = new Y() at «addr»:2424; + } + ''') + ] + + // Names that parse but are invalid. + validationError.forEach [ addr | + parseWithoutError(''' + target C; + reactor Y {} + federated reactor at foo@«addr»:4242 { + y = new Y() at «addr»:2424; + } + ''').assertWarning(LfPackage::eINSTANCE.host, null, + "Invalid host name or fully qualified domain name.") + ] + } + + /** + * Maps a type to a list of known good values. + */ + val primitiveTypeToKnownGood = #{ + PrimitiveType.BOOLEAN -> #["true", "\"true\"", "false", "\"false\""], + PrimitiveType.INTEGER -> #["0", "1", "\"42\"", "\"-1\"", "-2"], + PrimitiveType.NON_NEGATIVE_INTEGER -> #["0", "1", "42"], + PrimitiveType.TIME_VALUE -> #["1 msec", "2 sec"], + PrimitiveType.STRING -> #["1", "\"foo\"", "bar"], + PrimitiveType.FILE -> #["valid.file", "something.json", "\"foobar.proto\""] + } + + /** + * Maps a type to a list of known bad values. + */ + val primitiveTypeToKnownBad = #{ + PrimitiveType.BOOLEAN -> #["1 sec", "foo", "\"foo\"", "[1]", "{baz: 42}", "'c'"], + PrimitiveType.INTEGER -> #["foo", "\"bar\"", "1 sec", "[1, 2]", "{foo: \"bar\"}", "'c'"], + PrimitiveType.NON_NEGATIVE_INTEGER -> #["-42", "foo", "\"bar\"", "1 sec", "[1, 2]", "{foo: \"bar\"}", "'c'"], + PrimitiveType.TIME_VALUE -> #["foo", "\"bar\"", "\"3 sec\"", "\"4 weeks\"", "[1, 2]", "{foo: \"bar\"}", "'c'"], + PrimitiveType.STRING -> #["1 msec", "[1, 2]", "{foo: \"bar\"}", "'c'"] + } + + /** + * Maps a type to a list, each entry of which represents a list with + * three entries: a known wrong value, the suffix to add to the reported + * name, and the type that it should be. + */ + val compositeTypeToKnownBad = #{ + ArrayType.STRING_ARRAY -> #[ + #["[1 msec]", "[0]", PrimitiveType.STRING], + #["[foo, {bar: baz}]", "[1]", PrimitiveType.STRING], + #["{bar: baz}", "", ArrayType.STRING_ARRAY] + ], + UnionType.STRING_OR_STRING_ARRAY -> #[ + #["[1 msec]", "[0]", PrimitiveType.STRING], + #["[foo, {bar: baz}]", "[1]", PrimitiveType.STRING], + #["{bar: baz}", "", UnionType.STRING_OR_STRING_ARRAY] + ], + UnionType.FILE_OR_FILE_ARRAY -> #[ + #["[1 msec]", "[0]", PrimitiveType.FILE], + #["[foo, {bar: baz}]", "[1]", PrimitiveType.FILE], + #["{bar: baz}", "", UnionType.FILE_OR_FILE_ARRAY] + ], + UnionType.DOCKER_UNION -> #[ + #["foo", "", UnionType.DOCKER_UNION], + #["[1]", "", UnionType.DOCKER_UNION], + #["{bar: baz}", "", DictionaryType.DOCKER_DICT], + #["{FROM: [1, 2, 3]}", ".FROM", PrimitiveType.STRING] + ], + UnionType.TRACING_UNION -> #[ + #["foo", "", UnionType.TRACING_UNION], + #["[1]", "", UnionType.TRACING_UNION], + #["{bar: baz}", "", DictionaryType.TRACING_DICT], + #["{trace-file-name: [1, 2, 3]}", ".trace-file-name", PrimitiveType.STRING] + ] + } + + /** + * Given an array type, return a list of good or bad examples, + * depending on the value of the second parameter. + */ + def List synthesizeExamples(ArrayType type, boolean correct) { + val values = correct ? primitiveTypeToKnownGood : primitiveTypeToKnownBad + val examples = newLinkedList + if (correct) { + // Produce an array that has an entry for each value. + val entries = values.get(type.type) + if (!entries.nullOrEmpty) { + examples.add('''[«entries.join(', ')»]''') + } + } + return examples + } + + /** + * Given an union type, return a list of good or bad examples, + * depending on the value of the second parameter. + */ + def List synthesizeExamples(UnionType type, boolean correct) { + val examples = newLinkedList + if (correct) { + type.options.forEach [ + if (it instanceof TargetPropertyType) { + synthesizeExamples(it, correct).forEach [ + examples.add(it) + ] + } else { + // It must be a plain Enum + examples.add(it.toString()) + } + ] + } else { + // Return some obviously bad examples for the common + // case where the options are from an ordinary Enum. + if (!type.options.exists[it instanceof TargetPropertyType]) { + return #["foo", "\"bar\"", "1", "-1", + "{x: 42}", "[1, 2, 3]"] + } + } + return examples + } + + /** + * Given an union type, return a list of good or bad examples, + * depending on the value of the second parameter. + */ + def List synthesizeExamples(DictionaryType type, boolean correct) { + val examples = newLinkedList + // Produce a set of singleton dictionaries. + // If incorrect examples are wanted, garble the key. + type.options.forEach [ option | + synthesizeExamples(option.type, correct).forEach [ + examples.add('''{«option»«!correct? "iamwrong"»: «it»}''') + ] + ] + return examples + } + + /** + * Synthesize a list of values that either conform to the given type or + * do not, depending on whether the second argument 'correct' is true. + * Return an empty set if it is too complicated to generate examples + * (e.g., because the resulting errors are more sophisticated). + * + * Not all cases are covered by this function. Currently, the only cases not + * covered are known bad examples for composite types, which should be added + * to the compositeTypeToKnownBad map. + * + * @param correct True to synthesize correct examples automatically, otherwise + * synthesize incorrect examples. + */ + def List synthesizeExamples(TargetPropertyType type, boolean correct) { + if (type instanceof PrimitiveType) { + val values = correct ? primitiveTypeToKnownGood : primitiveTypeToKnownBad + val examples = values.get(type).toList + assertNotNull(examples) + return examples + } else { + if (type instanceof UnionType) { + return synthesizeExamples(type, correct) + } else if (type instanceof ArrayType) { + return synthesizeExamples(type, correct) + } else if (type instanceof DictionaryType) { + return synthesizeExamples(type, correct) + } else { + fail("Encountered an unknown type: " + type) + } + } + return #[] + } + + /** + * Create an LF program with the given key and value as a target property, + * parse it, and return the resulting model. + */ + def createModel(TargetProperty key, String value) { + val target = key.supportedBy.get(0).displayName + println('''«key»: «value»''') + return parseWithoutError(''' + target «target» {«key»: «value»}; + reactor Y {} + main reactor { + y = new Y() + } + ''') + } + + /** + * Perform checks on target properties. + */ + @Test + public void checkTargetProperties() { + + for (prop : TargetProperty.options) { + if (prop == TargetProperty.CARGO_DEPENDENCIES) { + // we test that separately as it has better error messages + return + } + println('''Testing target property «prop» which is «prop.type»''') + println("====") + println("Known good assignments:") + val knownCorrect = synthesizeExamples(prop.type, true) + knownCorrect.forEach [ + val model = prop.createModel(it) + model.assertNoErrors() + // Also make sure warnings are produced when files are not present. + if (prop.type == PrimitiveType.FILE) { + model.assertWarning( + LfPackage::eINSTANCE.keyValuePair, + null, '''Could not find file: '«it.withoutQuotes»'.''') + } + ] + // Extra checks for filenames. + // Temporarily disabled because we need a more sophisticated check that looks for files in different places. +// if (prop.type == prop.type == ArrayType.FILE_ARRAY || +// prop.type == UnionType.FILE_OR_FILE_ARRAY) { +// val model = prop.createModel( +// synthesizeExamples(ArrayType.FILE_ARRAY, true).get(0)) +// primitiveTypeToKnownGood.get(PrimitiveType.FILE).forEach [ +// model.assertWarning( +// LfPackage::eINSTANCE.keyValuePair, +// null, '''Could not find file: '«it.withoutQuotes»'.''') +// ] +// } + + println("Known bad assignments:") + val knownIncorrect = synthesizeExamples(prop.type, false) + if (!knownIncorrect.isNullOrEmpty) { + knownIncorrect.forEach [ + prop.createModel(it).assertError( + LfPackage::eINSTANCE.keyValuePair, + null, '''Target property '«prop.toString»' is required to be «prop.type».''') + ] + } else { + // No type was synthesized. It must be a composite type. + val list = compositeTypeToKnownBad.get(prop.type) + if (list === null) { + println('''No known incorrect values provided for target property '«prop»'. Aborting test.''') + assertTrue(false) + } else { + list.forEach [ + prop.createModel(it.get(0).toString). + assertError( + LfPackage::eINSTANCE.keyValuePair, + null, '''Target property '«prop.toString»«it.get(1)»' is required to be «it.get(2)».''') + ] + } + } + println("====") + } + println("Done!") + } + + + @Test + public void checkCargoDependencyProperty() { + val prop = TargetProperty.CARGO_DEPENDENCIES + val knownCorrect = #[ "{}", "{ dep: \"8.2\" }", "{ dep: { version: \"8.2\"} }", "{ dep: { version: \"8.2\", features: [\"foo\"]} }" ] + knownCorrect.forEach [ + prop.createModel(it).assertNoErrors() + ] + + // vvvvvvvvvvv + prop.createModel("{ dep: {/*empty*/} }") + .assertError(LfPackage::eINSTANCE.keyValuePairs, null, "Must specify one of 'version', 'path', or 'git'") + + // vvvvvvvvvvv + prop.createModel("{ dep: { unknown_key: \"\"} }") + .assertError(LfPackage::eINSTANCE.keyValuePair, null, "Unknown key: 'unknown_key'") + + // vvvv + prop.createModel("{ dep: { features: \"\" } }") + .assertError(LfPackage::eINSTANCE.element, null, "Expected an array of strings for key 'features'") + } +} + + + From 16668dba9a1b08e9b7dc8d783120e1012b943405 Mon Sep 17 00:00:00 2001 From: Hou Seng Wong Date: Sat, 22 Jan 2022 11:36:56 -0800 Subject: [PATCH 02/11] port more xtend --- .../compiler/LinguaFrancaValidationTest.java | 793 ++++++++++++------ 1 file changed, 520 insertions(+), 273 deletions(-) diff --git a/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaValidationTest.java b/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaValidationTest.java index 0e09a1cee2..415bbed1f6 100644 --- a/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaValidationTest.java +++ b/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaValidationTest.java @@ -45,11 +45,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assertions.fail; - -import static extension org.lflang.ASTUtils.*; +import org.lflang.ASTUtils.*; import org.lflang.TargetProperty.UnionType; import org.lflang.TargetProperty.ArrayType; import org.lflang.tests.LFInjectorProvider; @@ -696,24 +692,42 @@ public void detectCausalityLoop() { */ @Test public void afterBreaksCycle() { - parseWithoutError(''' - target C - - reactor X { - input x:int; - output y:int; - reaction(x) -> y {= - =} - } - - main reactor { - a = new X() - b = new X() - a.y -> b.x after 5 msec - b.y -> a.x - } - - ''').assertNoErrors() +// Java 17: +// String testCase = """ +// target C +// +// reactor X { +// input x:int; +// output y:int; +// reaction(x) -> y {= +// =} +// } +// +// main reactor { +// a = new X() +// b = new X() +// a.y -> b.x after 5 msec +// b.y -> a.x +// } +// """ +// Java 11: + String testCase = String.join(System.getProperty("line.separator"), + "target C", + " ", + "reactor X {", + " input x:int;", + " output y:int;", + " reaction(x) -> y {=", + " =}", + "}", + "", + "main reactor {", + " a = new X()", + " b = new X()", + " a.y -> b.x after 5 msec", + " b.y -> a.x", + "}"); + parseWithoutError(testCase).assertNoErrors(); } @@ -723,24 +737,43 @@ public void afterBreaksCycle() { */ @Test public void afterBreaksCycle2() { - parseWithoutError(''' - target C - - reactor X { - input x:int; - output y:int; - reaction(x) -> y {= - =} - } - - main reactor { - a = new X() - b = new X() - a.y -> b.x after 0 sec - b.y -> a.x - } - - ''').assertNoErrors() +// Java 17: +// String testCase = """ +// target C +// +// reactor X { +// input x:int; +// output y:int; +// reaction(x) -> y {= +// =} +// } +// +// main reactor { +// a = new X() +// b = new X() +// a.y -> b.x after 0 sec +// b.y -> a.x +// } +// """ +// Java 11: + + String testCase = String.join(System.getProperty("line.separator"), + "target C", + " ", + "reactor X {", + " input x:int;", + " output y:int;", + " reaction(x) -> y {=", + " =}", + "}", + "", + "main reactor {", + " a = new X()", + " b = new X()", + " a.y -> b.x after 0 sec", + " b.y -> a.x", + "}"); + parseWithoutError(testCase).assertNoErrors(); } @@ -750,24 +783,42 @@ public void afterBreaksCycle2() { */ @Test public void afterBreaksCycle3() { - parseWithoutError(''' - target C - - reactor X { - input x:int; - output y:int; - reaction(x) -> y {= - =} - } - - main reactor { - a = new X() - b = new X() - a.y -> b.x after 0 - b.y -> a.x - } - - ''').assertNoErrors() +// Java 17: +// String testCase = """ +// target C +// +// reactor X { +// input x:int; +// output y:int; +// reaction(x) -> y {= +// =} +// } +// +// main reactor { +// a = new X() +// b = new X() +// a.y -> b.x after 0 +// b.y -> a.x +// } +// """ +// Java 11: + String testCase = String.join(System.getProperty("line.separator"), + "target C", + " ", + "reactor X {", + " input x:int;", + " output y:int;", + " reaction(x) -> y {=", + " =}", + "}", + "", + "main reactor {", + " a = new X()", + " b = new X()", + " a.y -> b.x after 0", + " b.y -> a.x", + "}"); + parseWithoutError(testCase).assertNoErrors(); } @@ -776,24 +827,41 @@ public void afterBreaksCycle3() { */ @Test public void nonzeroAfterMustHaveUnits() { - parseWithoutError(''' - target C - - reactor X { - input x:int; - output y:int; - reaction(x) -> y {= - =} - } - - main reactor { - a = new X() - b = new X() - a.y -> b.x after 1 - } - - ''').assertError(LfPackage::eINSTANCE.time, - null, 'Missing time unit.') +// Java 17: +// String testCase = """ +// target C +// +// reactor X { +// input x:int; +// output y:int; +// reaction(x) -> y {= +// =} +// } +// +// main reactor { +// a = new X() +// b = new X() +// a.y -> b.x after 1 +// } +// """ +// Java 11: + String testCase = String.join(System.getProperty("line.separator"), + "target C", + " ", + "reactor X {", + " input x:int;", + " output y:int;", + " reaction(x) -> y {=", + " =}", + "}", + "", + "main reactor {", + " a = new X()", + " b = new X()", + " a.y -> b.x after 1", + "}"); + parseWithoutError(testCase).assertError(LfPackage::eINSTANCE.time, + null, 'Missing time unit.'); } @@ -804,15 +872,26 @@ public void nonzeroAfterMustHaveUnits() { */ @Test public void nonZeroTimeValueWithoutUnits() { - parseWithoutError(''' - target C; - main reactor { - timer t(42, 1 sec); - reaction(t) {= - printf("Hello World.\n"); - =} - } - ''').assertError(LfPackage::eINSTANCE.value, null, "Missing time unit.") +// Java 17: +// String testCase = """ +// target C; +// main reactor { +// timer t(42, 1 sec); +// reaction(t) {= +// printf("Hello World.\n"); +// =} +// } +// """ +// Java 11: + String testCase = String.join(System.getProperty("line.separator"), + "target C;", + "main reactor {", + " timer t(42, 1 sec);", + " reaction(t) {=", + " printf(\"Hello World.\n\");", + " =}", + "}"); + parseWithoutError(testCase).assertError(LfPackage::eINSTANCE.value, null, "Missing time unit."); } /** @@ -820,16 +899,27 @@ public void nonZeroTimeValueWithoutUnits() { */ @Test public void parameterTypeMismatch() { - parseWithoutError(''' - target C; - main reactor (p:int(0)) { - timer t(p, 1 sec); - reaction(t) {= - printf("Hello World.\n"); - =} - } - ''').assertError(LfPackage::eINSTANCE.value, - null, 'Parameter is not of time type') +// Java 17: +// String testCase = """ +// target C; +// main reactor (p:int(0)) { +// timer t(p, 1 sec); +// reaction(t) {= +// printf("Hello World.\n"); +// =} +// } +// """ +// Java 11: + String testCase = String.join(System.getProperty("line.separator"), + "target C;", + "main reactor (p:int(0)) {", + " timer t(p, 1 sec);", + " reaction(t) {=", + " printf(\"Hello World.\n\");", + " =}", + "}"); + parseWithoutError(testCase).assertError(LfPackage::eINSTANCE.value, + null, "Parameter is not of time type"); } @@ -838,16 +928,27 @@ main reactor (p:int(0)) { */ @Test public void targetCodeInTimeArgument() { - parseWithoutError(''' - target C; - main reactor { - timer t({=foo()=}, 1 sec); - reaction(t) {= - printf("Hello World.\n"); - =} - } - ''').assertError(LfPackage::eINSTANCE.value, - null, 'Invalid time literal') +// Java 17: +// String testCase = """ +// target C; +// main reactor { +// timer t({=foo()=}, 1 sec); +// reaction(t) {= +// printf("Hello World.\n"); +// =} +// } +// """ +// Java 11: + String testCase = String.join(System.getProperty("line.separator"), + "target C;", + "main reactor {", + " timer t({=foo()=}, 1 sec);", + " reaction(t) {=", + " printf(\"Hello World.\n\");", + " =}", + "}"); + parseWithoutError(testCase).assertError(LfPackage::eINSTANCE.value, + null, 'Invalid time literal'); } @@ -856,18 +957,30 @@ public void targetCodeInTimeArgument() { */ @Test public void overflowingDeadlineC() { - parseWithoutError(''' - target C; - main reactor { - timer t; - reaction(t) {= - printf("Hello World.\n"); - =} deadline (40 hours) {= - =} - } - ''').assertError(LfPackage::eINSTANCE.deadline, null, +// Java 17: +// String testCase = """ +// target C; +// main reactor { +// timer t; +// reaction(t) {= +// printf("Hello World.\n"); +// =} deadline (40 hours) {= +// =} +// } +// """ +// Java 11: + String testCase = String.join(System.getProperty("line.separator"), + "target C;", + "main reactor {", + "timer t;", + " reaction(t) {=", + " printf(\"Hello World.\n\");", + " =} deadline (40 hours) {=", + " =}", + "}"); + parseWithoutError(testCase).assertError(LfPackage::eINSTANCE.deadline, null, "Deadline exceeds the maximum of " + TimeValue.MAX_LONG_DEADLINE + - " nanoseconds.") + " nanoseconds."); } @@ -876,18 +989,32 @@ public void overflowingDeadlineC() { */ @Test public void overflowingParameterC() { - parseWithoutError(''' - target C; - main reactor(d:time(40 hours)) { - timer t; - reaction(t) {= - printf("Hello World.\n"); - =} deadline (d) {= - =} - } - ''').assertError(LfPackage::eINSTANCE.parameter, null, +// Java 17: +// String testCase = """ +// target C; +// main reactor(d:time(40 hours)) { +// timer t; +// reaction(t) {= +// printf("Hello World. +// "); +// =} deadline (d) {= +// =} +// } +// """ +// Java 11: + String testCase = String.join(System.getProperty("line.separator"), + "target C;", + "main reactor(d:time(40 hours)) {", + "timer t;", + " reaction(t) {=", + " printf(\"Hello World.", + "\");", + " =} deadline (d) {=", + " =}", + "}"); + parseWithoutError(testCase).assertError(LfPackage::eINSTANCE.parameter, null, "Time value used to specify a deadline exceeds the maximum of " + - TimeValue.MAX_LONG_DEADLINE + " nanoseconds.") + TimeValue.MAX_LONG_DEADLINE + " nanoseconds."); } @@ -896,21 +1023,36 @@ main reactor(d:time(40 hours)) { */ @Test public void overflowingAssignmentC() { - parseWithoutError(''' - target C; - reactor Print(d:time(39 hours)) { - timer t; - reaction(t) {= - printf("Hello World.\n"); - =} deadline (d) {= - =} - } - main reactor { - p = new Print(d=40 hours); - } - ''').assertError(LfPackage::eINSTANCE.assignment, null, +// Java 17: +// String testCase = """ +// target C; +// reactor Print(d:time(39 hours)) { +// timer t; +// reaction(t) {= +// printf("Hello World.\n"); +// =} deadline (d) {= +// =} +// } +// main reactor { +// p = new Print(d=40 hours); +// } +// """ +// Java 11: + String testCase = String.join(System.getProperty("line.separator"), + "target C;", + "reactor Print(d:time(39 hours)) {", + " timer t;", + " reaction(t) {=", + " printf(\"Hello World.\n\");", + " =} deadline (d) {=", + " =}", + "}", + "main reactor {", + " p = new Print(d=40 hours);", + "}"); + parseWithoutError(testCase).assertError(LfPackage::eINSTANCE.assignment, null, "Time value used to specify a deadline exceeds the maximum of " + - TimeValue.MAX_LONG_DEADLINE + " nanoseconds.") + TimeValue.MAX_LONG_DEADLINE + " nanoseconds."); } /** @@ -918,15 +1060,25 @@ reactor Print(d:time(39 hours)) { */ @Test public void missingTrigger() { - parseWithoutError(''' - target C; - reactor X { - reaction() {= - // - =} - } - ''').assertWarning(LfPackage::eINSTANCE.reaction, null, - "Reaction has no trigger.") +// Java 17: +// String testCase = """ +// target C; +// reactor X { +// reaction() {= +// // +// =} +// } +// """ +// Java 11: + String testCase = String.join(System.getProperty("line.separator"), + "target C;", + "reactor X {", + " reaction() {=", + " //", + " =}", + "}"); + parseWithoutError(testCase).assertWarning(LfPackage::eINSTANCE.reaction, null, + "Reaction has no trigger."); } /** @@ -936,47 +1088,67 @@ public void missingTrigger() { public void testPreambleVisibility() { for (target : Target.values) { for (visibility : Visibility.values) { - val model_reactor_scope = parseWithoutError(''' - target «target»; - reactor Foo { - «IF visibility != Visibility.NONE»«visibility» «ENDIF»preamble {==} - } - ''') - - val model_file_scope = parseWithoutError(''' - target «target»; - «IF visibility != Visibility.NONE»«visibility» «ENDIF»preamble {==} - reactor Foo { - } - ''') - - val model_no_preamble = parseWithoutError(''' - target «target»; - reactor Foo { - } - ''') +// Java 17: +// Model model_reactor_scope = """ +// target %s; +// reactor Foo { +// %spreamble {==} +// } +// """.formatted(target, visibility != java.beans.Visibility.NONE ? visibility + " " : ""); +// Java 11: + Model model_reactor_scope = parseWithoutError(String.join(System.getProperty("line.separator"), + String.format("target %s;", target), + "reactor Foo {", + String.format(" %spreamble {==}", visibility != java.beans.Visibility.NONE ? visibility + " " : ""), + "}")); + +// Java 17: +// Model model_file_scope = """ +// target %s; +// %spreamble {==} +// reactor Foo { +// } +// """.formatted(target, visibility != java.beans.Visibility.NONE ? visibility + " " : ""); +// Java 11: + Model model_file_scope = parseWithoutError(String.join(System.getProperty("line.separator"), + String.format("target %s;", target), + String.format(" %spreamble {==}", visibility != java.beans.Visibility.NONE ? visibility + " " : ""), + "reactor Foo {", + "}")); + +// Java 17: +// Model model_no_preamble = """ +// target %s; +// reactor Foo { +// } +// """.formatted(target); +// Java 11: + Model model_no_preamble = parseWithoutError(String.join(System.getProperty("line.separator"), + String.format("target %s;", target), + "reactor Foo {", + "}")); - model_no_preamble.assertNoIssues + model_no_preamble.assertNoIssues(); if (target == Target.CPP) { if (visibility == Visibility.NONE) { model_file_scope.assertError(LfPackage::eINSTANCE.preamble, null, - "Preambles for the C++ target need a visibility qualifier (private or public)!") + "Preambles for the C++ target need a visibility qualifier (private or public)!"); model_reactor_scope.assertError(LfPackage::eINSTANCE.preamble, null, - "Preambles for the C++ target need a visibility qualifier (private or public)!") + "Preambles for the C++ target need a visibility qualifier (private or public)!"); } else { - model_file_scope.assertNoIssues - model_reactor_scope.assertNoIssues + model_file_scope.assertNoIssues(); + model_reactor_scope.assertNoIssues(); } } else { if (visibility == Visibility.NONE) { - model_file_scope.assertNoIssues - model_reactor_scope.assertNoIssues + model_file_scope.assertNoIssues(); + model_reactor_scope.assertNoIssues(); } else { model_file_scope.assertWarning(LfPackage::eINSTANCE.preamble, null, - '''The «visibility» qualifier has no meaning for the «target.name» target. It should be removed.''') + String.format("The %s qualifier has no meaning for the %s target. It should be removed.", visibility, target.name)); model_reactor_scope.assertWarning(LfPackage::eINSTANCE.preamble, null, - '''The «visibility» qualifier has no meaning for the «target.name» target. It should be removed.''') + String.format("The %s qualifier has no meaning for the %s target. It should be removed.", visibility, target.name)); } } } @@ -989,41 +1161,59 @@ public void testPreambleVisibility() { */ @Test public void stateAndParameterDeclarationsInC() { - val model = parseWithoutError(''' - target C; - reactor Bar(a(0), // ERROR: type missing - b:int, // ERROR: uninitialized - t:time(42), // ERROR: units missing - x:int(0), - h:time("bla"), // ERROR: not a type - q:time(1 msec, 2 msec), // ERROR: not a list - y:int(t) // ERROR: init using parameter - ) { - state offset:time(42); // ERROR: units missing - state w:time(x); // ERROR: parameter is not a time - state foo:time("bla"); // ERROR: assigned value not a time - timer tick(1); // ERROR: not a time - } - ''') +// Java 17: +// String testCase = """ +// target C; +// reactor Bar(a(0), // ERROR: type missing +// b:int, // ERROR: uninitialized +// t:time(42), // ERROR: units missing +// x:int(0), +// h:time("bla"), // ERROR: not a type +// q:time(1 msec, 2 msec), // ERROR: not a list +// y:int(t) // ERROR: init using parameter +// ) { +// state offset:time(42); // ERROR: units missing +// state w:time(x); // ERROR: parameter is not a time +// state foo:time("bla"); // ERROR: assigned value not a time +// timer tick(1); // ERROR: not a time +// } +// """ +// Java 11: + String testCase = String.join(System.getProperty("line.separator"), + "target C;", + "reactor Bar(a(0), // ERROR: type missing", + " b:int, // ERROR: uninitialized", + " t:time(42), // ERROR: units missing", + " x:int(0),", + " h:time(\"bla\"), // ERROR: not a type ", + " q:time(1 msec, 2 msec), // ERROR: not a list", + " y:int(t) // ERROR: init using parameter", + ") {", + " state offset:time(42); // ERROR: units missing", + " state w:time(x); // ERROR: parameter is not a time", + " state foo:time(\"bla\"); // ERROR: assigned value not a time", + " timer tick(1); // ERROR: not a time", + "}"); + val model = parseWithoutError(testCase); model.assertError(LfPackage::eINSTANCE.parameter, null, - "Type declaration missing.") + "Type declaration missing."); model.assertError(LfPackage::eINSTANCE.parameter, null, - "Missing time unit.") + "Missing time unit."); model.assertError(LfPackage::eINSTANCE.parameter, null, - "Invalid time literal.") + "Invalid time literal."); model.assertError(LfPackage::eINSTANCE.parameter, null, - "Time parameter cannot be initialized using a list.") + "Time parameter cannot be initialized using a list."); model.assertError(LfPackage::eINSTANCE.parameter, null, - "Parameter cannot be initialized using parameter.") + "Parameter cannot be initialized using parameter."); model.assertError(LfPackage::eINSTANCE.stateVar, null, - "Referenced parameter does not denote a time.") + "Referenced parameter does not denote a time."); model.assertError(LfPackage::eINSTANCE.stateVar, null, - "Invalid time literal.") + "Invalid time literal."); model.assertError(LfPackage::eINSTANCE.parameter, null, - "Uninitialized parameter.") + "Uninitialized parameter."); model.assertError(LfPackage::eINSTANCE.value, null, - "Missing time unit.") + "Missing time unit."); } @@ -1032,45 +1222,73 @@ reactor Bar(a(0), // ERROR: type missing */ @Test public void recognizeIPV4() { - - val correct = #["127.0.0.1", "10.0.0.1", "192.168.1.1", "0.0.0.0", - "192.168.1.1"] - val parseError = #["10002.3.4", "1.2.3.4.5"] - val validationError = #["256.0.0.0", "260.0.0.0"] + List correct = List.of("127.0.0.1", "10.0.0.1", "192.168.1.1", "0.0.0.0", "192.168.1.1"); + List parseError = List.of("10002.3.4", "1.2.3.4.5"); + List validationError = List.of("256.0.0.0", "260.0.0.0"); // Correct IP addresses. - correct.forEach [ addr | - parseWithoutError(''' - target C; - reactor Y {} - federated reactor X at foo@«addr»:4242 { - y = new Y() at «addr»:2424; - } - ''') - ] + for (String addr : correct) { +// Java 17: +// String testCase = """ +// target C; +// reactor Y {} +// federated reactor X at foo@%s:4242 { +// y = new Y() at %s:2424; +// } +// """.formatted(addr, addr); +// Java 11: + parseWithoutError( + String.join(System.getProperty("line.separator"), + "target C;", + "reactor Y {}", + String.format("federated reactor X at foo@%s:4242 {", addr), + String.format(" y = new Y() at %s:2424; ", addr), + "}") + ); + } // IP addresses that don't parse. - parseError.forEach [ addr | - parseWithError(''' - target C; - reactor Y {} - federated reactor X at foo@«addr»:4242 { - y = new Y() at «addr»:2424; - } - ''') - ] + for (String addr : parseError) { +// Java 17: +// String testCase = """ +// target C; +// reactor Y {} +// federated reactor X at foo@%s:4242 { +// y = new Y() at %s:2424; +// } +// """.formatted(addr, addr); +// Java 11: + parseWithError( + String.join(System.getProperty("line.separator"), + "target C;", + "reactor Y {}", + String.format("federated reactor X at foo@%s:4242 {", addr), + String.format(" y = new Y() at %s:2424; ", addr), + "}") + ); + } // IP addresses that parse but are invalid. - validationError.forEach [ addr | - parseWithoutError(''' - target C; - reactor Y {} - federated reactor X at foo@«addr»:4242 { - y = new Y() at «addr»:2424; - } - ''').assertWarning(LfPackage::eINSTANCE.host, null, - "Invalid IP address.") - ] + for (String addr : validationError) { +// Java 17: +// String testCase = """ +// target C; +// reactor Y {} +// federated reactor X at foo@%s:4242 { +// y = new Y() at %s:2424; +// } +// """.formatted(addr, addr); +// Java 11: + parseWithoutError( + String.join(System.getProperty("line.separator"), + "target C;", + "reactor Y {}", + String.format("federated reactor X at foo@%s:4242 {", addr), + String.format(" y = new Y() at %s:2424; ", addr), + "}") + ).assertWarning(LfPackage::eINSTANCE.host, null, + "Invalid IP address."); + } } /** @@ -1078,8 +1296,7 @@ public void recognizeIPV4() { */ @Test public void recognizeIPV6() { - - val correct = #["1:2:3:4:5:6:7:8", "1:2:3:4:5:6:7::", "1:2:3:4:5:6::8", + List correct = List.of("1:2:3:4:5:6:7:8", "1:2:3:4:5:6:7::", "1:2:3:4:5:6::8", "1:2:3:4:5::8", "1:2:3:4::8", "1:2:3::8", "1:2::8", "1::8", "::8", "::", "1::3:4:5:6:7:8", "1::4:5:6:7:8", "1::5:6:7:8", "1::6:7:8", "1::7:8", "1::8", "1::", "1:2:3:4:5::7:8", "1:2:3:4::6:7:8", @@ -1090,48 +1307,78 @@ public void recognizeIPV6() { "::ffff:000:255.255.255.0", "::ffff:0000:255.255.255.0", "::ffff:0.0.0.0", "::ffff:1.2.3.4", "::ffff:10.0.0.1", "1:2:3:4:5:6:77:88", "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", - "2001:db8:3:4::192.0.2.33", "64:ff9b::192.0.2.33", "0:0:0:0:0:0:10.0.0.1"] + "2001:db8:3:4::192.0.2.33", "64:ff9b::192.0.2.33", "0:0:0:0:0:0:10.0.0.1"); - val validationError = #["1:2:3:4:5:6:7:8:9", "1:2:3:4:5:6::7:8", + List validationError = List.of("1:2:3:4:5:6:7:8:9", "1:2:3:4:5:6::7:8", "1:2:3:4:5:6:7:8:", "::1:2:3:4:5:6:7:8", "1:2:3:4:5:6:7:8::", "1:2:3:4:5:6:7:88888", "2001:db8:3:4:5::192.0.2.33", - "fe08::7:8interface", "fe08::7:8interface", "fe08::7:8i"] + "fe08::7:8interface", "fe08::7:8interface", "fe08::7:8i"); - val parseError = #["fe08::7:8%", ":1:2:3:4:5:6:7:8"] + List parseError = List.of("fe08::7:8%", ":1:2:3:4:5:6:7:8"); // Correct IP addresses. - correct.forEach [ addr | - parseWithoutError(''' - target C; - reactor Y {} - federated reactor at [foo@«addr»]:4242 { - y = new Y() at [«addr»]:2424; - } - ''').assertNoIssues() - ] - + for (String addr : correct) { +// Java 17: +// String testCase = """ +// target C; +// reactor Y {} +// federated reactor X at [foo@%s]:4242 { +// y = new Y() at [%s]:2424; +// } +// """.formatted(addr, addr); +// Java 11: + parseWithoutError( + String.join(System.getProperty("line.separator"), + "target C;", + "reactor Y {}", + String.format("federated reactor X at [foo@%s]:4242 {", addr), + String.format(" y = new Y() at [%s]:2424; ", addr), + "}") + ).assertNoIssues(); + } + + // IP addresses that don't parse. - parseError.forEach [ addr | - parseWithError(''' - target C; - reactor Y {} - federated reactor at [foo@«addr»]:4242 { - y = new Y() at [«addr»]:2424; - } - ''') - ] - + for (String addr : parseError) { +// Java 17: +// String testCase = """ +// target C; +// reactor Y {} +// federated reactor X at [foo@%s]:4242 { +// y = new Y() at [%s]:2424; +// } +// """.formatted(addr, addr); +// Java 11: + parseWithError( + String.join(System.getProperty("line.separator"), + "target C;", + "reactor Y {}", + String.format("federated reactor X at [foo@%s]:4242 {", addr), + String.format(" y = new Y() at [%s]:2424; ", addr), + "}") + ); + } + // IP addresses that parse but are invalid. - validationError.forEach [ addr | - parseWithoutError(''' - target C; - reactor Y {} - federated reactor at [foo@«addr»]:4242 { - y = new Y() at [«addr»]:2424; - } - ''').assertWarning(LfPackage::eINSTANCE.host, null, - "Invalid IP address.") - ] + for (String addr : validationError) { +// Java 17: +// String testCase = """ +// target C; +// reactor Y {} +// federated reactor X at [foo@%s]:4242 { +// y = new Y() at [%s]:2424; +// } +// """.formatted(addr, addr); +// Java 11: + parseWithoutError( + String.join(System.getProperty("line.separator"), + "target C;", + "reactor Y {}", + String.format("federated reactor X at [foo@%s]:4242 {", addr), + String.format(" y = new Y() at [%s]:2424; ", addr), + "}") + ).assertWarning(LfPackage::eINSTANCE.host, null, "Invalid IP address."); + } } /** From 533e61dfbdcd829e67cd34a90f350fcde00b46fc Mon Sep 17 00:00:00 2001 From: Hou Seng Wong Date: Sat, 22 Jan 2022 12:58:41 -0800 Subject: [PATCH 03/11] fix some syntax errors --- .../compiler/LinguaFrancaValidationTest.java | 473 ++++++++++-------- 1 file changed, 261 insertions(+), 212 deletions(-) diff --git a/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaValidationTest.java b/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaValidationTest.java index 415bbed1f6..83f61edbf5 100644 --- a/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaValidationTest.java +++ b/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaValidationTest.java @@ -28,6 +28,8 @@ import com.google.inject.Inject; import java.util.List; +import java.util.Map; +import java.util.LinkedList; import org.eclipse.xtext.testing.InjectWith; import org.eclipse.xtext.testing.extensions.InjectionExtension; import org.eclipse.xtext.testing.util.ParseHelper; @@ -74,11 +76,11 @@ public class LinguaFrancaValidationTest { * @return A model representing the parsed string. */ private Model parseWithoutError(String s) { - Model model = parser.parse(s); + Model model = tryToParse(s); Assertions.assertNotNull(model); - Assertions.assertTrue(model.eResource.errors.isEmpty, + Assertions.assertTrue(model.eResource().getErrors().isEmpty(), "Encountered unexpected error while parsing: " + - model.eResource.errors); + model.eResource().getErrors()); return model; } @@ -87,12 +89,25 @@ private Model parseWithoutError(String s) { * @return A model representing the parsed string. */ private Model parseWithError(String s) { - Model model = parser.parse(s); + Model model = tryToParse(s); Assertions.assertNotNull(model); - Assertions.assertFalse(model.eResource.errors.isEmpty); + Assertions.assertFalse(model.eResource().getErrors().isEmpty()); return model; } + /* Helper function to try to parse a Lingua Franca program. + * @return A model representing the parsed string, or null if program cannot be parsed. + */ + private Model tryToParse(String s) { + Model model; + try { + model = parser.parse(s); + } catch (Exception e) { + model = null; + } + return model; + } + /** * Ensure that duplicate identifiers for actions reported. @@ -114,8 +129,10 @@ public void duplicateVariable() { " logical action bar;", " physical action bar;", "}"); - parseWithoutError(testCase).assertError(LfPackage::eINSTANCE.action, null, - "Duplicate Variable 'bar' in Reactor 'Foo'"); + validator.assertError(parseWithoutError(testCase), + LfPackage.eINSTANCE.getAction(), + null, + "Duplicate Variable 'bar' in Reactor 'Foo'"); } @@ -131,11 +148,11 @@ public void disallowReactorCalledPreamble() { // } // """ // Java 11: - Model model_no_errors = parser.parse(String.join( - System.getProperty("line.separator"), - "target Cpp;", - "main reactor {", - "}" + Model model_no_errors = tryToParse(String.join( + System.getProperty("line.separator"), + "target Cpp;", + "main reactor {", + "}" )); // Java 17: @@ -145,7 +162,7 @@ public void disallowReactorCalledPreamble() { // } // """ // Java 11: - Model model_error_1 = parser.parse(String.join( + Model model_error_1 = tryToParse(String.join( System.getProperty("line.separator"), "target Cpp;", "main reactor Preamble {", @@ -161,7 +178,7 @@ public void disallowReactorCalledPreamble() { // } // """ // Java 11: - Model model_error_2 = parser.parse(String.join( + Model model_error_2 = tryToParse(String.join( System.getProperty("line.separator"), "target Cpp;", "reactor Preamble {", @@ -173,18 +190,22 @@ public void disallowReactorCalledPreamble() { Assertions.assertNotNull(model_no_errors); Assertions.assertNotNull(model_error_1); Assertions.assertNotNull(model_error_2); - Assertions.assertTrue(model_no_errors.eResource.errors.isEmpty, - "Encountered unexpected error while parsing: " + model_no_errors.eResource.errors); - Assertions.assertTrue(model_error_1.eResource.errors.isEmpty, - "Encountered unexpected error while parsing: " + model_error_1.eResource.errors); - Assertions.assertTrue(model_error_2.eResource.errors.isEmpty, - "Encountered unexpected error while parsing: " + model_error_2.eResource.errors); + Assertions.assertTrue(model_no_errors.eResource().getErrors().isEmpty(), + "Encountered unexpected error while parsing: " + model_no_errors.eResource().getErrors()); + Assertions.assertTrue(model_error_1.eResource().getErrors().isEmpty(), + "Encountered unexpected error while parsing: " + model_error_1.eResource().getErrors()); + Assertions.assertTrue(model_error_2.eResource().getErrors().isEmpty(), + "Encountered unexpected error while parsing: " + model_error_2.eResource().getErrors()); - model_no_errors.assertNoIssues(); - model_error_1.assertError(LfPackage::eINSTANCE.reactor, null, - "Reactor cannot be named 'Preamble'"); - model_error_2.assertError(LfPackage::eINSTANCE.reactor, null, - "Reactor cannot be named 'Preamble'"); + validator.assertNoIssues(model_no_errors); + validator.assertError(model_error_1, + LfPackage.eINSTANCE.getReactor(), + null, + "Reactor cannot be named 'Preamble'"); + validator.assertError(model_error_2, + LfPackage.eINSTANCE.getReactor(), + null, + "Reactor cannot be named 'Preamble'"); } @@ -206,8 +227,7 @@ public void disallowUnderscoreInputs() { "main reactor {", " input __bar;", "}"); - - parseWithoutError(testCase).assertError(LfPackage::eINSTANCE.input, null, + validator.assertError(parseWithoutError(testCase), LfPackage.eINSTANCE.getInput(), null, "Names of objects (inputs, outputs, actions, timers, parameters, state, reactor definitions, and reactor instantiation) may not start with \"__\": __bar"); } @@ -224,7 +244,7 @@ public void disallowMainWithDifferentNameThanFile() { "main reactor Foo {}" ); - parseWithoutError(testCase).assertError(LfPackage::eINSTANCE.reactor, null, + validator.assertError(parseWithoutError(testCase), LfPackage.eINSTANCE.getReactor(), null, "Name of main reactor must match the file name (or be omitted)"); } @@ -248,7 +268,7 @@ public void disallowUnderscoreOutputs() { " output __bar;", "}"); - parseWithoutError(testCase).assertError(LfPackage::eINSTANCE.output, null, + validator.assertError(parseWithoutError(testCase), LfPackage.eINSTANCE.getOutput(), null, "Names of objects (inputs, outputs, actions, timers, parameters, state, reactor definitions, and reactor instantiation) may not start with \"__\": __bar"); } @@ -270,8 +290,8 @@ public void disallowUnderscoreActions() { "main reactor Foo {", " logical action __bar;", "}"); - parseWithoutError(testCase).assertError(LfPackage::eINSTANCE.action, null, - "Names of objects (inputs, outputs, actions, timers, parameters, state, reactor definitions, and reactor instantiation) may not start with \"__\": __bar"); + validator.assertError(parseWithoutError(testCase), LfPackage.eINSTANCE.getAction(), null, + "Names of objects (inputs, outputs, actions, timers, parameters, state, reactor definitions, and reactor instantiation) may not start with \"__\": __bar"); } /** @@ -292,8 +312,8 @@ public void disallowUnderscoreTimers() { "main reactor Foo {", " timer __bar(0);", "}"); - parseWithoutError(testCase).assertError(LfPackage::eINSTANCE.timer, null, - "Names of objects (inputs, outputs, actions, timers, parameters, state, reactor definitions, and reactor instantiation) may not start with \"__\": __bar"); + validator.assertError(parseWithoutError(testCase), LfPackage.eINSTANCE.getTimer(), null, + "Names of objects (inputs, outputs, actions, timers, parameters, state, reactor definitions, and reactor instantiation) may not start with \"__\": __bar"); } /** @@ -312,7 +332,7 @@ public void disallowUnderscoreParameters() { "target TypeScript;", "main reactor Foo(__bar) {", "}"); - parseWithoutError(testCase).assertError(LfPackage::eINSTANCE.parameter, null, + validator.assertError(parseWithoutError(testCase), LfPackage.eINSTANCE.getParameter(), null, "Names of objects (inputs, outputs, actions, timers, parameters, state, reactor definitions, and reactor instantiation) may not start with \"__\": __bar"); } @@ -334,7 +354,7 @@ public void disallowUnderscoreStates() { "main reactor Foo {", " state __bar;", "}"); - parseWithoutError(testCase).assertError(LfPackage::eINSTANCE.stateVar, null, + validator.assertError(parseWithoutError(testCase), LfPackage.eINSTANCE.getStateVar(), null, "Names of objects (inputs, outputs, actions, timers, parameters, state, reactor definitions, and reactor instantiation) may not start with \"__\": __bar"); } @@ -354,7 +374,7 @@ public void disallowUnderscoreReactorDef() { "target TypeScript;", "main reactor __Foo {", "}"); - parseWithoutError(testCase).assertError(LfPackage::eINSTANCE.reactor, null, + validator.assertError(parseWithoutError(testCase), LfPackage.eINSTANCE.getReactor(), null, "Names of objects (inputs, outputs, actions, timers, parameters, state, reactor definitions, and reactor instantiation) may not start with \"__\": __Foo"); } @@ -380,7 +400,7 @@ public void disallowUnderscoreReactorInstantiation() { "main reactor Bar {", " __x = new Foo();", "}"); - parseWithoutError(testCase).assertError(LfPackage::eINSTANCE.instantiation, null, + validator.assertError(parseWithoutError(testCase), LfPackage.eINSTANCE.getInstantiation(), null, "Names of objects (inputs, outputs, actions, timers, parameters, state, reactor definitions, and reactor instantiation) may not start with \"__\": __x"); } @@ -416,7 +436,7 @@ public void connectionToEffectPort() { " reaction(startup) -> out {=", " =}", "}"); - parseWithoutError(testCase).assertError(LfPackage::eINSTANCE.connection, null, + validator.assertError(parseWithoutError(testCase), LfPackage.eINSTANCE.getConnection(), null, "Cannot connect: Port named 'out' is already effect of a reaction."); } @@ -451,14 +471,14 @@ public void connectionToEffectPort2() { "}", "main reactor {", " output out:int;", - " x = new Foo();" + " x = new Foo();", " y = new Foo();", "", " y.out -> x.inp;", " reaction(startup) -> x.inp {=", " =}", "}"); - parseWithoutError(testCase).assertError(LfPackage::eINSTANCE.connection, null, + validator.assertError(parseWithoutError(testCase), LfPackage.eINSTANCE.getConnection(), null, "Cannot connect: Port named 'inp' is already effect of a reaction."); } @@ -492,13 +512,13 @@ public void connectionToEffectPort3() { "}", "main reactor {", " input in:int;", - " x1 = new Foo();" + " x1 = new Foo();", " x2 = new Foo();", " in -> x1.in;", " reaction(startup) -> x2.in {=", " =}", "}"); - parseWithoutError(testCase).assertNoErrors(); + validator.assertNoErrors(parseWithoutError(testCase)); } /** @@ -530,12 +550,12 @@ public void connectionToEffectPort4() { "}", "main reactor {", " input in:int;", - " x1 = new Foo();" + " x1 = new Foo();", " in -> x1.in;", " reaction(startup) -> x1.in {=", " =}", "}"); - parseWithoutError(testCase).assertError(LfPackage::eINSTANCE.connection, null, + validator.assertError(parseWithoutError(testCase), LfPackage.eINSTANCE.getConnection(), null, "Cannot connect: Port named 'in' is already effect of a reaction."); } @@ -569,7 +589,7 @@ public void multipleConnectionsToInputTest() { "}", "reactor Sink {", " input in:int;", - "}" + "}", "main reactor {", " input in:int;", " src = new Source();", @@ -577,7 +597,7 @@ public void multipleConnectionsToInputTest() { " in -> sink.in;", " src.out -> sink.in;", "}"); - parseWithoutError(testCase).assertError(LfPackage::eINSTANCE.connection, null, + validator.assertError(parseWithoutError(testCase), LfPackage.eINSTANCE.getConnection(), null, "Cannot connect: Port named 'in' may only appear once on the right side of a connection."); } @@ -599,7 +619,7 @@ public void detectInstantiationCycle() { "reactor Contained {", " x = new Contained();", "}"); - parseWithoutError(testCase).assertError(LfPackage::eINSTANCE.instantiation, + validator.assertError(parseWithoutError(testCase), LfPackage.eINSTANCE.getInstantiation(), null, "Instantiation is part of a cycle: Contained"); } @@ -632,9 +652,9 @@ public void detectInstantiationCycle2() { " x = new Intermediate();", "}" )); - model.assertError(LfPackage::eINSTANCE.instantiation, + validator.assertError(model, LfPackage.eINSTANCE.getInstantiation(), null, "Instantiation is part of a cycle: Intermediate, Contained."); - model.assertError(LfPackage::eINSTANCE.instantiation, + validator.assertError(model, LfPackage.eINSTANCE.getInstantiation(), null, "Instantiation is part of a cycle: Intermediate, Contained."); } @@ -680,11 +700,10 @@ public void detectCausalityLoop() { " b.y -> a.x", "}" )); - model.assertError(LfPackage::eINSTANCE.reaction, + validator.assertError(LfPackage.eINSTANCE.getReaction(), null, "Reaction triggers involved in cyclic dependency in reactor X: x."); - model.assertError(LfPackage::eINSTANCE.reaction, - null, "Reaction effects involved in cyclic dependency in reactor X: y."); - + validator.assertError(LfPackage.eINSTANCE.getReaction(), + null, "Reaction effects involved in cyclic dependency in reactor X: y."); } /** @@ -727,8 +746,7 @@ public void afterBreaksCycle() { " a.y -> b.x after 5 msec", " b.y -> a.x", "}"); - parseWithoutError(testCase).assertNoErrors(); - + validator.assertNoErrors(parseWithoutError(testCase)); } @@ -773,8 +791,7 @@ public void afterBreaksCycle2() { " a.y -> b.x after 0 sec", " b.y -> a.x", "}"); - parseWithoutError(testCase).assertNoErrors(); - + validator.assertNoErrors(parseWithoutError(testCase)); } @@ -818,8 +835,7 @@ public void afterBreaksCycle3() { " a.y -> b.x after 0", " b.y -> a.x", "}"); - parseWithoutError(testCase).assertNoErrors(); - + validator.assertNoErrors(parseWithoutError(testCase)); } /** @@ -860,9 +876,8 @@ public void nonzeroAfterMustHaveUnits() { " b = new X()", " a.y -> b.x after 1", "}"); - parseWithoutError(testCase).assertError(LfPackage::eINSTANCE.time, - null, 'Missing time unit.'); - + validator.assertError(parseWithoutError(testCase), LfPackage.eINSTANCE.getTime(), + null, "Missing time unit."); } @@ -891,7 +906,7 @@ public void nonZeroTimeValueWithoutUnits() { " printf(\"Hello World.\n\");", " =}", "}"); - parseWithoutError(testCase).assertError(LfPackage::eINSTANCE.value, null, "Missing time unit."); + validator.assertError(parseWithoutError(testCase), LfPackage.eINSTANCE.getValue(), null, "Missing time unit."); } /** @@ -918,9 +933,8 @@ public void parameterTypeMismatch() { " printf(\"Hello World.\n\");", " =}", "}"); - parseWithoutError(testCase).assertError(LfPackage::eINSTANCE.value, + validator.assertError(parseWithoutError(testCase), LfPackage.eINSTANCE.getValue(), null, "Parameter is not of time type"); - } /** @@ -947,8 +961,8 @@ public void targetCodeInTimeArgument() { " printf(\"Hello World.\n\");", " =}", "}"); - parseWithoutError(testCase).assertError(LfPackage::eINSTANCE.value, - null, 'Invalid time literal'); + validator.assertError(parseWithoutError(testCase), LfPackage.eINSTANCE.getValue(), + null, "Invalid time literal"); } @@ -978,9 +992,9 @@ public void overflowingDeadlineC() { " =} deadline (40 hours) {=", " =}", "}"); - parseWithoutError(testCase).assertError(LfPackage::eINSTANCE.deadline, null, + validator.assertError(parseWithoutError(testCase), LfPackage.eINSTANCE.getDeadline(), null, "Deadline exceeds the maximum of " + TimeValue.MAX_LONG_DEADLINE + - " nanoseconds."); + " nanoseconds."); } @@ -1012,9 +1026,9 @@ public void overflowingParameterC() { " =} deadline (d) {=", " =}", "}"); - parseWithoutError(testCase).assertError(LfPackage::eINSTANCE.parameter, null, + validator.assertError(parseWithoutError(testCase), LfPackage.eINSTANCE.getParameter(), null, "Time value used to specify a deadline exceeds the maximum of " + - TimeValue.MAX_LONG_DEADLINE + " nanoseconds."); + TimeValue.MAX_LONG_DEADLINE + " nanoseconds."); } @@ -1050,9 +1064,9 @@ public void overflowingAssignmentC() { "main reactor {", " p = new Print(d=40 hours);", "}"); - parseWithoutError(testCase).assertError(LfPackage::eINSTANCE.assignment, null, + validator.assertError(parseWithoutError(testCase), LfPackage.eINSTANCE.getAssignment(), null, "Time value used to specify a deadline exceeds the maximum of " + - TimeValue.MAX_LONG_DEADLINE + " nanoseconds."); + TimeValue.MAX_LONG_DEADLINE + " nanoseconds."); } /** @@ -1077,7 +1091,7 @@ public void missingTrigger() { " //", " =}", "}"); - parseWithoutError(testCase).assertWarning(LfPackage::eINSTANCE.reaction, null, + validator.assertWarning(parseWithoutError(testCase), LfPackage.eINSTANCE.getReaction(), null, "Reaction has no trigger."); } @@ -1086,8 +1100,8 @@ public void missingTrigger() { */ @Test public void testPreambleVisibility() { - for (target : Target.values) { - for (visibility : Visibility.values) { + for (Target target : Target.values()) { + for (Visibility visibility : Visibility.values()) { // Java 17: // Model model_reactor_scope = """ // target %s; @@ -1099,7 +1113,7 @@ public void testPreambleVisibility() { Model model_reactor_scope = parseWithoutError(String.join(System.getProperty("line.separator"), String.format("target %s;", target), "reactor Foo {", - String.format(" %spreamble {==}", visibility != java.beans.Visibility.NONE ? visibility + " " : ""), + String.format(" %spreamble {==}", visibility != Visibility.NONE ? visibility + " " : ""), "}")); // Java 17: @@ -1112,7 +1126,7 @@ public void testPreambleVisibility() { // Java 11: Model model_file_scope = parseWithoutError(String.join(System.getProperty("line.separator"), String.format("target %s;", target), - String.format(" %spreamble {==}", visibility != java.beans.Visibility.NONE ? visibility + " " : ""), + String.format(" %spreamble {==}", visibility != Visibility.NONE ? visibility + " " : ""), "reactor Foo {", "}")); @@ -1128,27 +1142,27 @@ public void testPreambleVisibility() { "reactor Foo {", "}")); - model_no_preamble.assertNoIssues(); + validator.assertNoIssues(model_no_preamble); if (target == Target.CPP) { if (visibility == Visibility.NONE) { - model_file_scope.assertError(LfPackage::eINSTANCE.preamble, null, + validator.assertError(model_file_scope, LfPackage.eINSTANCE.getPreamble(), null, "Preambles for the C++ target need a visibility qualifier (private or public)!"); - model_reactor_scope.assertError(LfPackage::eINSTANCE.preamble, null, - "Preambles for the C++ target need a visibility qualifier (private or public)!"); + validator.assertError(model_reactor_scope, LfPackage.eINSTANCE.getPreamble(), null, + "Preambles for the C++ target need a visibility qualifier (private or public)!"); } else { - model_file_scope.assertNoIssues(); - model_reactor_scope.assertNoIssues(); + validator.assertNoIssues(model_file_scope); + validator.assertNoIssues(model_reactor_scope); } } else { if (visibility == Visibility.NONE) { - model_file_scope.assertNoIssues(); - model_reactor_scope.assertNoIssues(); + validator.assertNoIssues(model_file_scope); + validator.assertNoIssues(model_reactor_scope); } else { - model_file_scope.assertWarning(LfPackage::eINSTANCE.preamble, null, - String.format("The %s qualifier has no meaning for the %s target. It should be removed.", visibility, target.name)); - model_reactor_scope.assertWarning(LfPackage::eINSTANCE.preamble, null, - String.format("The %s qualifier has no meaning for the %s target. It should be removed.", visibility, target.name)); + validator.assertWarning(model_file_scope, LfPackage.eINSTANCE.getPreamble(), null, + String.format("The %s qualifier has no meaning for the %s target. It should be removed.", visibility, target.name())); + validator.assertWarning(model_reactor_scope, LfPackage.eINSTANCE.getPreamble(), null, + String.format("The %s qualifier has no meaning for the %s target. It should be removed.", visibility, target.name())); } } } @@ -1194,25 +1208,26 @@ public void stateAndParameterDeclarationsInC() { " state foo:time(\"bla\"); // ERROR: assigned value not a time", " timer tick(1); // ERROR: not a time", "}"); - val model = parseWithoutError(testCase); + Model model = parseWithoutError(testCase); - model.assertError(LfPackage::eINSTANCE.parameter, null, + + validator.assertError(model, LfPackage.eINSTANCE.getParameter(), null, "Type declaration missing."); - model.assertError(LfPackage::eINSTANCE.parameter, null, + validator.assertError(model, LfPackage.eINSTANCE.getParameter(), null, "Missing time unit."); - model.assertError(LfPackage::eINSTANCE.parameter, null, + validator.assertError(model, LfPackage.eINSTANCE.getParameter(), null, "Invalid time literal."); - model.assertError(LfPackage::eINSTANCE.parameter, null, + validator.assertError(model, LfPackage.eINSTANCE.getParameter(), null, "Time parameter cannot be initialized using a list."); - model.assertError(LfPackage::eINSTANCE.parameter, null, + validator.assertError(model, LfPackage.eINSTANCE.getParameter(), null, "Parameter cannot be initialized using parameter."); - model.assertError(LfPackage::eINSTANCE.stateVar, null, + validator.assertError(model, LfPackage.eINSTANCE.getStateVar(), null, "Referenced parameter does not denote a time."); - model.assertError(LfPackage::eINSTANCE.stateVar, null, + validator.assertError(model, LfPackage.eINSTANCE.getStateVar(), null, "Invalid time literal."); - model.assertError(LfPackage::eINSTANCE.parameter, null, + validator.assertError(model, LfPackage.eINSTANCE.getParameter(), null, "Uninitialized parameter."); - model.assertError(LfPackage::eINSTANCE.value, null, + validator.assertError(model, LfPackage.eINSTANCE.getValue(), null, "Missing time unit."); } @@ -1271,7 +1286,7 @@ public void recognizeIPV4() { // IP addresses that parse but are invalid. for (String addr : validationError) { // Java 17: -// String testCase = """ +// Model model = """ // target C; // reactor Y {} // federated reactor X at foo@%s:4242 { @@ -1279,15 +1294,15 @@ public void recognizeIPV4() { // } // """.formatted(addr, addr); // Java 11: - parseWithoutError( + Model model = parseWithoutError( String.join(System.getProperty("line.separator"), "target C;", "reactor Y {}", String.format("federated reactor X at foo@%s:4242 {", addr), String.format(" y = new Y() at %s:2424; ", addr), "}") - ).assertWarning(LfPackage::eINSTANCE.host, null, - "Invalid IP address."); + ); + validator.assertWarning(model, LfPackage.eINSTANCE.getHost(), null, "Invalid IP address."); } } @@ -1327,14 +1342,15 @@ public void recognizeIPV6() { // } // """.formatted(addr, addr); // Java 11: - parseWithoutError( + Model model = parseWithoutError( String.join(System.getProperty("line.separator"), "target C;", "reactor Y {}", String.format("federated reactor X at [foo@%s]:4242 {", addr), String.format(" y = new Y() at [%s]:2424; ", addr), "}") - ).assertNoIssues(); + ); + validator.assertNoIssues(model); } @@ -1370,14 +1386,15 @@ public void recognizeIPV6() { // } // """.formatted(addr, addr); // Java 11: - parseWithoutError( + Model model = parseWithoutError( String.join(System.getProperty("line.separator"), "target C;", "reactor Y {}", String.format("federated reactor X at [foo@%s]:4242 {", addr), String.format(" y = new Y() at [%s]:2424; ", addr), "}") - ).assertWarning(LfPackage::eINSTANCE.host, null, "Invalid IP address."); + ); + validator.assertWarning(model, LfPackage.eINSTANCE.getHost(), null, "Invalid IP address."); } } @@ -1387,128 +1404,160 @@ public void recognizeIPV6() { @Test public void recognizeHostNames() { - val correct = #["localhost"] // FIXME: add more + List correct = List.of("localhost"); // FIXME: add more - val validationError = #["x.y.z"] // FIXME: add more + List validationError = List.of("x.y.z"); // FIXME: add more - val parseError = #["..xyz"] // FIXME: add more - + List parseError = List.of("..xyz"); // FIXME: add more + + // Correct names. - correct.forEach [ addr | - parseWithoutError(''' - target C; - reactor Y {} - federated reactor at foo@«addr»:4242 { - y = new Y() at «addr»:2424; - } - ''').assertNoIssues() - ] - + for (String addr : correct) { +// Java 17: +// String testCase = """ +// target C; +// reactor Y {} +// federated reactor X at foo@%s:4242 { +// y = new Y() at %s:2424; +// } +// """.formatted(addr, addr); +// Java 11: + parseWithoutError( + String.join(System.getProperty("line.separator"), + "target C;", + "reactor Y {}", + String.format("federated reactor X at foo@%s:4242 {", addr), + String.format(" y = new Y() at %s:2424; ", addr), + "}") + ); + } + // Names that don't parse. - parseError.forEach [ addr | - parseWithError(''' - target C; - reactor Y {} - federated reactor at foo@«addr»:4242 { - y = new Y() at «addr»:2424; - } - ''') - ] - + for (String addr : parseError) { +// Java 17: +// String testCase = """ +// target C; +// reactor Y {} +// federated reactor X at foo@%s:4242 { +// y = new Y() at %s:2424; +// } +// """.formatted(addr, addr); +// Java 11: + parseWithError( + String.join(System.getProperty("line.separator"), + "target C;", + "reactor Y {}", + String.format("federated reactor X at foo@%s:4242 {", addr), + String.format(" y = new Y() at %s:2424; ", addr), + "}") + ); + } + // Names that parse but are invalid. - validationError.forEach [ addr | - parseWithoutError(''' - target C; - reactor Y {} - federated reactor at foo@«addr»:4242 { - y = new Y() at «addr»:2424; - } - ''').assertWarning(LfPackage::eINSTANCE.host, null, - "Invalid host name or fully qualified domain name.") - ] + for (String addr : validationError) { +// Java 17: +// String testCase = """ +// target C; +// reactor Y {} +// federated reactor X at foo@%s:4242 { +// y = new Y() at %s:2424; +// } +// """.formatted(addr, addr); +// Java 11: + Model model = parseWithoutError( + String.join(System.getProperty("line.separator"), + "target C;", + "reactor Y {}", + String.format("federated reactor X at foo@%s:4242 {", addr), + String.format(" y = new Y() at %s:2424; ", addr), + "}") + ); + validator.assertWarning(model, LfPackage.eINSTANCE.getHost(), null, + "Invalid host name or fully qualified domain name."); + } } /** * Maps a type to a list of known good values. */ - val primitiveTypeToKnownGood = #{ - PrimitiveType.BOOLEAN -> #["true", "\"true\"", "false", "\"false\""], - PrimitiveType.INTEGER -> #["0", "1", "\"42\"", "\"-1\"", "-2"], - PrimitiveType.NON_NEGATIVE_INTEGER -> #["0", "1", "42"], - PrimitiveType.TIME_VALUE -> #["1 msec", "2 sec"], - PrimitiveType.STRING -> #["1", "\"foo\"", "bar"], - PrimitiveType.FILE -> #["valid.file", "something.json", "\"foobar.proto\""] - } - + Map> primitiveTypeToKnownGood = Map.of( + PrimitiveType.BOOLEAN, List.of("true", "\"true\"", "false", "\"false\""), + PrimitiveType.INTEGER, List.of("0", "1", "\"42\"", "\"-1\"", "-2"), + PrimitiveType.NON_NEGATIVE_INTEGER, List.of("0", "1", "42"), + PrimitiveType.TIME_VALUE, List.of("1 msec", "2 sec"), + PrimitiveType.STRING, List.of("1", "\"foo\"", "bar"), + PrimitiveType.FILE, List.of("valid.file", "something.json", "\"foobar.proto\"") + ); + /** * Maps a type to a list of known bad values. */ - val primitiveTypeToKnownBad = #{ - PrimitiveType.BOOLEAN -> #["1 sec", "foo", "\"foo\"", "[1]", "{baz: 42}", "'c'"], - PrimitiveType.INTEGER -> #["foo", "\"bar\"", "1 sec", "[1, 2]", "{foo: \"bar\"}", "'c'"], - PrimitiveType.NON_NEGATIVE_INTEGER -> #["-42", "foo", "\"bar\"", "1 sec", "[1, 2]", "{foo: \"bar\"}", "'c'"], - PrimitiveType.TIME_VALUE -> #["foo", "\"bar\"", "\"3 sec\"", "\"4 weeks\"", "[1, 2]", "{foo: \"bar\"}", "'c'"], - PrimitiveType.STRING -> #["1 msec", "[1, 2]", "{foo: \"bar\"}", "'c'"] - } - + Map> primitiveTypeToKnownBad = Map.of( + PrimitiveType.BOOLEAN, List.of("1 sec", "foo", "\"foo\"", "[1]", "{baz: 42}", "'c'"), + PrimitiveType.INTEGER, List.of("foo", "\"bar\"", "1 sec", "[1, 2]", "{foo: \"bar\"}", "'c'"), + PrimitiveType.NON_NEGATIVE_INTEGER, List.of("-42", "foo", "\"bar\"", "1 sec", "[1, 2]", "{foo: \"bar\"}", "'c'"), + PrimitiveType.TIME_VALUE, List.of("foo", "\"bar\"", "\"3 sec\"", "\"4 weeks\"", "[1, 2]", "{foo: \"bar\"}", "'c'"), + PrimitiveType.STRING, List.of("1 msec", "[1, 2]", "{foo: \"bar\"}", "'c'") + ); + /** * Maps a type to a list, each entry of which represents a list with * three entries: a known wrong value, the suffix to add to the reported * name, and the type that it should be. */ - val compositeTypeToKnownBad = #{ - ArrayType.STRING_ARRAY -> #[ - #["[1 msec]", "[0]", PrimitiveType.STRING], - #["[foo, {bar: baz}]", "[1]", PrimitiveType.STRING], - #["{bar: baz}", "", ArrayType.STRING_ARRAY] - ], - UnionType.STRING_OR_STRING_ARRAY -> #[ - #["[1 msec]", "[0]", PrimitiveType.STRING], - #["[foo, {bar: baz}]", "[1]", PrimitiveType.STRING], - #["{bar: baz}", "", UnionType.STRING_OR_STRING_ARRAY] - ], - UnionType.FILE_OR_FILE_ARRAY -> #[ - #["[1 msec]", "[0]", PrimitiveType.FILE], - #["[foo, {bar: baz}]", "[1]", PrimitiveType.FILE], - #["{bar: baz}", "", UnionType.FILE_OR_FILE_ARRAY] - ], - UnionType.DOCKER_UNION -> #[ - #["foo", "", UnionType.DOCKER_UNION], - #["[1]", "", UnionType.DOCKER_UNION], - #["{bar: baz}", "", DictionaryType.DOCKER_DICT], - #["{FROM: [1, 2, 3]}", ".FROM", PrimitiveType.STRING] - ], - UnionType.TRACING_UNION -> #[ - #["foo", "", UnionType.TRACING_UNION], - #["[1]", "", UnionType.TRACING_UNION], - #["{bar: baz}", "", DictionaryType.TRACING_DICT], - #["{trace-file-name: [1, 2, 3]}", ".trace-file-name", PrimitiveType.STRING] - ] - } + Map>> compositeTypeToKnownBad = Map.of( + ArrayType.STRING_ARRAY, List.of( + List.of("[1 msec]", "[0]", PrimitiveType.STRING), + List.of("[foo, {bar: baz}]", "[1]", PrimitiveType.STRING), + List.of("{bar: baz}", "", ArrayType.STRING_ARRAY) + ), + UnionType.STRING_OR_STRING_ARRAY, List.of( + List.of("[1 msec]", "[0]", PrimitiveType.STRING), + List.of("[foo, {bar: baz}]", "[1]", PrimitiveType.STRING), + List.of("{bar: baz}", "", UnionType.STRING_OR_STRING_ARRAY) + ), + UnionType.FILE_OR_FILE_ARRAY, List.of( + List.of("[1 msec]", "[0]", PrimitiveType.FILE), + List.of("[foo, {bar: baz}]", "[1]", PrimitiveType.FILE), + List.of("{bar: baz}", "", UnionType.FILE_OR_FILE_ARRAY) + ), + UnionType.DOCKER_UNION, List.of( + List.of("foo", "", UnionType.DOCKER_UNION), + List.of("[1]", "", UnionType.DOCKER_UNION), + List.of("{bar: baz}", "", DictionaryType.DOCKER_DICT), + List.of("{FROM: [1, 2, 3]}", ".FROM", PrimitiveType.STRING) + ), + UnionType.TRACING_UNION, List.of( + List.of("foo", "", UnionType.TRACING_UNION), + List.of("[1]", "", UnionType.TRACING_UNION), + List.of("{bar: baz}", "", DictionaryType.TRACING_DICT), + List.of("{trace-file-name: [1, 2, 3]}", ".trace-file-name", PrimitiveType.STRING) + ) + ); /** * Given an array type, return a list of good or bad examples, * depending on the value of the second parameter. */ - def List synthesizeExamples(ArrayType type, boolean correct) { - val values = correct ? primitiveTypeToKnownGood : primitiveTypeToKnownBad - val examples = newLinkedList - if (correct) { - // Produce an array that has an entry for each value. - val entries = values.get(type.type) - if (!entries.nullOrEmpty) { - examples.add('''[«entries.join(', ')»]''') - } - } - return examples + private List synthesizeExamples(ArrayType type, boolean correct) { + Map> values = correct ? primitiveTypeToKnownGood : primitiveTypeToKnownBad; + List examples = new LinkedList<>(); + if (correct) { + // Produce an array that has an entry for each value. + List entries = values.get(type.type); + if (!(entries == null || entries.isEmpty())) { + examples.add(String.format("[%s]", String.join(", ", entries))); + } + } + return examples; } /** * Given an union type, return a list of good or bad examples, * depending on the value of the second parameter. */ - def List synthesizeExamples(UnionType type, boolean correct) { - val examples = newLinkedList + private List synthesizeExamples(UnionType type, boolean correct) { + List examples = new LinkedList<>(); if (correct) { type.options.forEach [ if (it instanceof TargetPropertyType) { @@ -1535,7 +1584,7 @@ def List synthesizeExamples(UnionType type, boolean correct) { * Given an union type, return a list of good or bad examples, * depending on the value of the second parameter. */ - def List synthesizeExamples(DictionaryType type, boolean correct) { + private List synthesizeExamples(DictionaryType type, boolean correct) { val examples = newLinkedList // Produce a set of singleton dictionaries. // If incorrect examples are wanted, garble the key. @@ -1560,7 +1609,7 @@ def List synthesizeExamples(DictionaryType type, boolean correct) { * @param correct True to synthesize correct examples automatically, otherwise * synthesize incorrect examples. */ - def List synthesizeExamples(TargetPropertyType type, boolean correct) { + private List synthesizeExamples(TargetPropertyType type, boolean correct) { if (type instanceof PrimitiveType) { val values = correct ? primitiveTypeToKnownGood : primitiveTypeToKnownBad val examples = values.get(type).toList @@ -1584,7 +1633,7 @@ def List synthesizeExamples(TargetPropertyType type, boolean correct) { * Create an LF program with the given key and value as a target property, * parse it, and return the resulting model. */ - def createModel(TargetProperty key, String value) { + private void createModel(TargetProperty key, String value) { val target = key.supportedBy.get(0).displayName println('''«key»: «value»''') return parseWithoutError(''' @@ -1617,7 +1666,7 @@ public void checkTargetProperties() { // Also make sure warnings are produced when files are not present. if (prop.type == PrimitiveType.FILE) { model.assertWarning( - LfPackage::eINSTANCE.keyValuePair, + LfPackage.eINSTANCE.keyValuePair, null, '''Could not find file: '«it.withoutQuotes»'.''') } ] @@ -1629,7 +1678,7 @@ public void checkTargetProperties() { // synthesizeExamples(ArrayType.FILE_ARRAY, true).get(0)) // primitiveTypeToKnownGood.get(PrimitiveType.FILE).forEach [ // model.assertWarning( -// LfPackage::eINSTANCE.keyValuePair, +// LfPackage.eINSTANCE.keyValuePair, // null, '''Could not find file: '«it.withoutQuotes»'.''') // ] // } @@ -1639,7 +1688,7 @@ public void checkTargetProperties() { if (!knownIncorrect.isNullOrEmpty) { knownIncorrect.forEach [ prop.createModel(it).assertError( - LfPackage::eINSTANCE.keyValuePair, + LfPackage.eINSTANCE.keyValuePair, null, '''Target property '«prop.toString»' is required to be «prop.type».''') ] } else { @@ -1652,7 +1701,7 @@ public void checkTargetProperties() { list.forEach [ prop.createModel(it.get(0).toString). assertError( - LfPackage::eINSTANCE.keyValuePair, + LfPackage.eINSTANCE.keyValuePair, null, '''Target property '«prop.toString»«it.get(1)»' is required to be «it.get(2)».''') ] } @@ -1665,7 +1714,7 @@ public void checkTargetProperties() { @Test public void checkCargoDependencyProperty() { - val prop = TargetProperty.CARGO_DEPENDENCIES + val prop = TargetProperty.CARGO_DEPENDENCIES; val knownCorrect = #[ "{}", "{ dep: \"8.2\" }", "{ dep: { version: \"8.2\"} }", "{ dep: { version: \"8.2\", features: [\"foo\"]} }" ] knownCorrect.forEach [ prop.createModel(it).assertNoErrors() @@ -1673,15 +1722,15 @@ public void checkCargoDependencyProperty() { // vvvvvvvvvvv prop.createModel("{ dep: {/*empty*/} }") - .assertError(LfPackage::eINSTANCE.keyValuePairs, null, "Must specify one of 'version', 'path', or 'git'") + .assertError(LfPackage.eINSTANCE.keyValuePairs, null, "Must specify one of 'version', 'path', or 'git'") // vvvvvvvvvvv prop.createModel("{ dep: { unknown_key: \"\"} }") - .assertError(LfPackage::eINSTANCE.keyValuePair, null, "Unknown key: 'unknown_key'") + .assertError(LfPackage.eINSTANCE.keyValuePair, null, "Unknown key: 'unknown_key'") // vvvv prop.createModel("{ dep: { features: \"\" } }") - .assertError(LfPackage::eINSTANCE.element, null, "Expected an array of strings for key 'features'") + .assertError(LfPackage.eINSTANCE.element, null, "Expected an array of strings for key 'features'") } } From e7ee1926bc63f83829b9073c234a64c0fd227921 Mon Sep 17 00:00:00 2001 From: Hou Seng Wong Date: Sat, 22 Jan 2022 13:29:05 -0800 Subject: [PATCH 04/11] port more xtend to java --- .../compiler/LinguaFrancaValidationTest.java | 120 ++++++++++-------- 1 file changed, 64 insertions(+), 56 deletions(-) diff --git a/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaValidationTest.java b/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaValidationTest.java index 83f61edbf5..0159693846 100644 --- a/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaValidationTest.java +++ b/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaValidationTest.java @@ -50,6 +50,7 @@ import org.lflang.ASTUtils.*; import org.lflang.TargetProperty.UnionType; import org.lflang.TargetProperty.ArrayType; +import org.lflang.TargetProperty.DictionaryElement; import org.lflang.tests.LFInjectorProvider; @ExtendWith(InjectionExtension.class) @@ -1557,27 +1558,27 @@ private List synthesizeExamples(ArrayType type, boolean correct) { * depending on the value of the second parameter. */ private List synthesizeExamples(UnionType type, boolean correct) { - List examples = new LinkedList<>(); - if (correct) { - type.options.forEach [ - if (it instanceof TargetPropertyType) { - synthesizeExamples(it, correct).forEach [ - examples.add(it) - ] - } else { - // It must be a plain Enum - examples.add(it.toString()) - } - ] - } else { - // Return some obviously bad examples for the common - // case where the options are from an ordinary Enum. - if (!type.options.exists[it instanceof TargetPropertyType]) { - return #["foo", "\"bar\"", "1", "-1", - "{x: 42}", "[1, 2, 3]"] - } - } - return examples + // val examples = newLinkedList + // if (correct) { + // type.options.forEach [ + // if (it instanceof TargetPropertyType) { + // synthesizeExamples(it, correct).forEach [ + // examples.add(it) + // ] + // } else { + // // It must be a plain Enum + // examples.add(it.toString()) + // } + // ] + // } else { + // // Return some obviously bad examples for the common + // // case where the options are from an ordinary Enum. + // if (!type.options.exists[it instanceof TargetPropertyType]) { + // return #["foo", "\"bar\"", "1", "-1", + // "{x: 42}", "[1, 2, 3]"] + // } + // } + // return examples } /** @@ -1585,15 +1586,15 @@ private List synthesizeExamples(UnionType type, boolean correct) { * depending on the value of the second parameter. */ private List synthesizeExamples(DictionaryType type, boolean correct) { - val examples = newLinkedList - // Produce a set of singleton dictionaries. - // If incorrect examples are wanted, garble the key. - type.options.forEach [ option | - synthesizeExamples(option.type, correct).forEach [ - examples.add('''{«option»«!correct? "iamwrong"»: «it»}''') - ] - ] - return examples + // val examples = newLinkedList + // // Produce a set of singleton dictionaries. + // // If incorrect examples are wanted, garble the key. + // type.options.forEach [ option | + // synthesizeExamples(option.type, correct).forEach [ + // examples.add('''{«option»«!correct? "iamwrong"»: «it»}''') + // ] + // ] + // return examples } /** @@ -1611,38 +1612,46 @@ private List synthesizeExamples(DictionaryType type, boolean correct) { */ private List synthesizeExamples(TargetPropertyType type, boolean correct) { if (type instanceof PrimitiveType) { - val values = correct ? primitiveTypeToKnownGood : primitiveTypeToKnownBad - val examples = values.get(type).toList - assertNotNull(examples) - return examples + Map> values = correct ? primitiveTypeToKnownGood : primitiveTypeToKnownBad; + List examples = values.get(type); + Assertions.assertNotNull(examples); + return examples; } else { if (type instanceof UnionType) { - return synthesizeExamples(type, correct) + return synthesizeExamples(type, correct); } else if (type instanceof ArrayType) { - return synthesizeExamples(type, correct) + return synthesizeExamples(type, correct); } else if (type instanceof DictionaryType) { - return synthesizeExamples(type, correct) + return synthesizeExamples(type, correct); } else { - fail("Encountered an unknown type: " + type) + Assertions.fail("Encountered an unknown type: " + type); } } - return #[] + return new LinkedList<>(); } /** * Create an LF program with the given key and value as a target property, * parse it, and return the resulting model. */ - private void createModel(TargetProperty key, String value) { - val target = key.supportedBy.get(0).displayName - println('''«key»: «value»''') - return parseWithoutError(''' - target «target» {«key»: «value»}; - reactor Y {} - main reactor { - y = new Y() - } - ''') + private Model createModel(TargetProperty key, String value) { + String target = key.supportedBy.get(0).getDisplayName(); + System.out.println(String.format("%s: %s", key, value)); +// Java 17: +// Model model = """ +// target %s {%s: %s}; +// reactor Y {} +// main reactor { +// y = new Y() +// } +// """.formatted(target, key, value); +// Java 11: + return parseWithoutError(String.join(System.getProperty("line.separator"), + String.format("target %s {%s: %s};", target, key, value), + "reactor Y {}", + "main reactor {", + " y = new Y() ", + "}")); } /** @@ -1650,16 +1659,15 @@ private void createModel(TargetProperty key, String value) { */ @Test public void checkTargetProperties() { - - for (prop : TargetProperty.options) { + for (TargetProperty prop : TargetProperty.getOptions()) { if (prop == TargetProperty.CARGO_DEPENDENCIES) { // we test that separately as it has better error messages - return + return; } - println('''Testing target property «prop» which is «prop.type»''') - println("====") - println("Known good assignments:") - val knownCorrect = synthesizeExamples(prop.type, true) + System.out.println(String.format("Testing target property %s which is %s", prop, prop.type)); + System.out.println("===="); + System.out.println("Known good assignments:"); + List knownCorrect = synthesizeExamples(prop.type, true); knownCorrect.forEach [ val model = prop.createModel(it) model.assertNoErrors() From 4bcfb2cdbeb27843d506fa1bc523b8c5f0f3bbc8 Mon Sep 17 00:00:00 2001 From: Hou Seng Wong Date: Sat, 22 Jan 2022 14:35:44 -0800 Subject: [PATCH 05/11] port ParsingTest to java --- ...est.xtend => LinguaFrancaParsingTest.java} | 65 ++++++++++++------- 1 file changed, 43 insertions(+), 22 deletions(-) rename org.lflang.tests/src/org/lflang/tests/compiler/{LinguaFrancaParsingTest.xtend => LinguaFrancaParsingTest.java} (50%) diff --git a/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaParsingTest.xtend b/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaParsingTest.java similarity index 50% rename from org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaParsingTest.xtend rename to org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaParsingTest.java index 7ec389b2af..a50f5b669d 100644 --- a/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaParsingTest.xtend +++ b/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaParsingTest.java @@ -24,37 +24,58 @@ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ***************/ -package org.lflang.tests.compiler +package org.lflang.tests.compiler; -import com.google.inject.Inject -import org.eclipse.xtext.testing.InjectWith -import org.eclipse.xtext.testing.extensions.InjectionExtension -import org.eclipse.xtext.testing.util.ParseHelper -import org.lflang.lf.Model -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.Assertions -import org.junit.jupiter.api.^extension.ExtendWith -import org.lflang.tests.LFInjectorProvider +import com.google.inject.Inject; +import org.eclipse.xtext.testing.InjectWith; +import org.eclipse.xtext.testing.extensions.InjectionExtension; +import org.eclipse.xtext.testing.util.ParseHelper; +import org.lflang.lf.Model; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.extension.ExtendWith; +import org.lflang.tests.LFInjectorProvider; -@ExtendWith(InjectionExtension) -@InjectWith(LFInjectorProvider) +@ExtendWith(InjectionExtension.class) +@InjectWith(LFInjectorProvider.class) /** * Test harness for ensuring that grammar captures * all corner cases. */ class LinguaFrancaParsingTest { - @Inject extension ParseHelper + @Inject + ParseHelper parser; @Test - def void checkForTarget() { - val result = ''' - targett C; - reactor Foo { - } - '''.parse - Assertions.assertNotNull(result) - val errors = result.eResource.errors - Assertions.assertFalse(errors.isEmpty, "Failed to catch misspelled target keyword.") + public void checkForTarget() { +// Java 17: +// String testCase = """ +// targett C; +// reactor Foo { +// } +// """ +// Java 11: + String testCase = String.join(System.getProperty("line.separator"), + "targett C;", + "reactor Foo {", + "}" + ); + Model result = tryToParse(testCase); + Assertions.assertNotNull(result); + Assertions.assertFalse(result.eResource().getErrors().isEmpty(), "Failed to catch misspelled target keyword.") + } + + /* Helper function to try to parse a Lingua Franca program. + * @return A model representing the parsed string, or null if program cannot be parsed. + */ + private Model tryToParse(String s) { + Model model; + try { + model = parser.parse(s); + } catch (Exception e) { + model = null; + } + return model; } } \ No newline at end of file From d74d345a6519ff029847216c03340f4dc8fc5f4d Mon Sep 17 00:00:00 2001 From: Hou Seng Wong Date: Sat, 22 Jan 2022 14:45:24 -0800 Subject: [PATCH 06/11] throw exception --- .../compiler/LinguaFrancaValidationTest.java | 108 ++++++++---------- 1 file changed, 47 insertions(+), 61 deletions(-) diff --git a/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaValidationTest.java b/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaValidationTest.java index 0159693846..2801932889 100644 --- a/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaValidationTest.java +++ b/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaValidationTest.java @@ -47,7 +47,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; -import org.lflang.ASTUtils.*; +import static org.lflang.ASTUtils.*; import org.lflang.TargetProperty.UnionType; import org.lflang.TargetProperty.ArrayType; import org.lflang.TargetProperty.DictionaryElement; @@ -76,8 +76,8 @@ public class LinguaFrancaValidationTest { * Helper function to parse a Lingua Franca program and expect no errors. * @return A model representing the parsed string. */ - private Model parseWithoutError(String s) { - Model model = tryToParse(s); + private Model parseWithoutError(String s) throws Exception { + Model model = parser.parse(s); Assertions.assertNotNull(model); Assertions.assertTrue(model.eResource().getErrors().isEmpty(), "Encountered unexpected error while parsing: " + @@ -89,32 +89,18 @@ private Model parseWithoutError(String s) { * Helper function to parse a Lingua Franca program and expect errors. * @return A model representing the parsed string. */ - private Model parseWithError(String s) { - Model model = tryToParse(s); + private Model parseWithError(String s) throws Exception { + Model model = parser.parse(s); Assertions.assertNotNull(model); Assertions.assertFalse(model.eResource().getErrors().isEmpty()); return model; } - /* Helper function to try to parse a Lingua Franca program. - * @return A model representing the parsed string, or null if program cannot be parsed. - */ - private Model tryToParse(String s) { - Model model; - try { - model = parser.parse(s); - } catch (Exception e) { - model = null; - } - return model; - } - - /** * Ensure that duplicate identifiers for actions reported. */ @Test - public void duplicateVariable() { + public void duplicateVariable() throws Exception { // Java 17: // String testCase = """ // target TypeScript; @@ -141,7 +127,7 @@ public void duplicateVariable() { * Check that reactors in C++ cannot be named preamble */ @Test - public void disallowReactorCalledPreamble() { + public void disallowReactorCalledPreamble() throws Exception { // Java 17: // Model model_no_errors = """ // target Cpp; @@ -149,7 +135,7 @@ public void disallowReactorCalledPreamble() { // } // """ // Java 11: - Model model_no_errors = tryToParse(String.join( + Model model_no_errors = parser.parse(String.join( System.getProperty("line.separator"), "target Cpp;", "main reactor {", @@ -163,7 +149,7 @@ public void disallowReactorCalledPreamble() { // } // """ // Java 11: - Model model_error_1 = tryToParse(String.join( + Model model_error_1 = parser.parse(String.join( System.getProperty("line.separator"), "target Cpp;", "main reactor Preamble {", @@ -179,7 +165,7 @@ public void disallowReactorCalledPreamble() { // } // """ // Java 11: - Model model_error_2 = tryToParse(String.join( + Model model_error_2 = parser.parse(String.join( System.getProperty("line.separator"), "target Cpp;", "reactor Preamble {", @@ -214,7 +200,7 @@ public void disallowReactorCalledPreamble() { * Ensure that "__" is not allowed at the start of an input name. */ @Test - public void disallowUnderscoreInputs() { + public void disallowUnderscoreInputs() throws Exception { // Java 17: // String testCase = """ // target TypeScript; @@ -233,7 +219,7 @@ public void disallowUnderscoreInputs() { } @Test - public void disallowMainWithDifferentNameThanFile() { + public void disallowMainWithDifferentNameThanFile() throws Exception { // Java 17: // String testCase = """ // target C; @@ -254,7 +240,7 @@ public void disallowMainWithDifferentNameThanFile() { * Ensure that "__" is not allowed at the start of an output name. */ @Test - public void disallowUnderscoreOutputs() { + public void disallowUnderscoreOutputs() throws Exception { // Java 17: // String testCase = """ // target TypeScript; @@ -277,7 +263,7 @@ public void disallowUnderscoreOutputs() { * Ensure that "__" is not allowed at the start of an action name. */ @Test - public void disallowUnderscoreActions() { + public void disallowUnderscoreActions() throws Exception { // Java 17: // String testCase = """ // target TypeScript; @@ -299,7 +285,7 @@ public void disallowUnderscoreActions() { * Ensure that "__" is not allowed at the start of a timer name. */ @Test - public void disallowUnderscoreTimers() { + public void disallowUnderscoreTimers() throws Exception { // Java 17: // String testCase = """ // target TypeScript; @@ -321,7 +307,7 @@ public void disallowUnderscoreTimers() { * Ensure that "__" is not allowed at the start of a parameter name. */ @Test - public void disallowUnderscoreParameters() { + public void disallowUnderscoreParameters() throws Exception { // Java 17: // String testCase = """ // target TypeScript; @@ -341,7 +327,7 @@ public void disallowUnderscoreParameters() { * Ensure that "__" is not allowed at the start of an state name. */ @Test - public void disallowUnderscoreStates() { + public void disallowUnderscoreStates() throws Exception { // Java 17: // String testCase = """ // target TypeScript; @@ -363,7 +349,7 @@ public void disallowUnderscoreStates() { * Ensure that "__" is not allowed at the start of a reactor definition name. */ @Test - public void disallowUnderscoreReactorDef() { + public void disallowUnderscoreReactorDef() throws Exception { // Java 17: // String testCase = """ // target TypeScript; @@ -383,7 +369,7 @@ public void disallowUnderscoreReactorDef() { * Ensure that "__" is not allowed at the start of a reactor instantiation name. */ @Test - public void disallowUnderscoreReactorInstantiation() { + public void disallowUnderscoreReactorInstantiation() throws Exception { // Java 17: // String testCase = """ // target TypeScript; @@ -409,7 +395,7 @@ public void disallowUnderscoreReactorInstantiation() { * Disallow connection to port that is effect of reaction. */ @Test - public void connectionToEffectPort() { + public void connectionToEffectPort() throws Exception { // Java 17: // String testCase = """ // target C; @@ -445,7 +431,7 @@ public void connectionToEffectPort() { * Disallow connection to port that is effect of reaction. */ @Test - public void connectionToEffectPort2() { + public void connectionToEffectPort2() throws Exception { // Java 17: // String testCase = """ // target C; @@ -487,7 +473,7 @@ public void connectionToEffectPort2() { * Allow connection to the port of a contained reactor if another port with same name is effect of a reaction. */ @Test - public void connectionToEffectPort3() { + public void connectionToEffectPort3() throws Exception { // Java 17: // String testCase = """ // target C; @@ -526,7 +512,7 @@ public void connectionToEffectPort3() { * Disallow connection to the port of a contained reactor if the same port is effect of a reaction. */ @Test - public void connectionToEffectPort4() { + public void connectionToEffectPort4() throws Exception { // Java 17: // String testCase = """ // target C; @@ -564,7 +550,7 @@ public void connectionToEffectPort4() { * Disallow connection of multiple ports to the same input port. */ @Test - public void multipleConnectionsToInputTest() { + public void multipleConnectionsToInputTest() throws Exception { // Java 17: // String testCase = """ // target C; @@ -606,7 +592,7 @@ public void multipleConnectionsToInputTest() { * Detect cycles in the instantiation graph. */ @Test - public void detectInstantiationCycle() { + public void detectInstantiationCycle() throws Exception { // Java 17: // String testCase = """ // target C; @@ -629,7 +615,7 @@ public void detectInstantiationCycle() { * Detect cycles in the instantiation graph. */ @Test - public void detectInstantiationCycle2() { + public void detectInstantiationCycle2() throws Exception { // Java 17: // String testCase = """ // target C; @@ -663,7 +649,7 @@ public void detectInstantiationCycle2() { * Detect causality loop. */ @Test - public void detectCausalityLoop() { + public void detectCausalityLoop() throws Exception { // Java 17: // String testCase = """ // target C; @@ -701,9 +687,9 @@ public void detectCausalityLoop() { " b.y -> a.x", "}" )); - validator.assertError(LfPackage.eINSTANCE.getReaction(), + validator.assertError(model, LfPackage.eINSTANCE.getReaction(), null, "Reaction triggers involved in cyclic dependency in reactor X: x."); - validator.assertError(LfPackage.eINSTANCE.getReaction(), + validator.assertError(model, LfPackage.eINSTANCE.getReaction(), null, "Reaction effects involved in cyclic dependency in reactor X: y."); } @@ -711,7 +697,7 @@ public void detectCausalityLoop() { * Let cyclic dependencies be broken by "after" clauses. */ @Test - public void afterBreaksCycle() { + public void afterBreaksCycle() throws Exception { // Java 17: // String testCase = """ // target C @@ -755,7 +741,7 @@ public void afterBreaksCycle() { * Let cyclic dependencies be broken by "after" clauses with zero delay. */ @Test - public void afterBreaksCycle2() { + public void afterBreaksCycle2() throws Exception { // Java 17: // String testCase = """ // target C @@ -800,7 +786,7 @@ public void afterBreaksCycle2() { * Let cyclic dependencies be broken by "after" clauses with zero delay and no units. */ @Test - public void afterBreaksCycle3() { + public void afterBreaksCycle3() throws Exception { // Java 17: // String testCase = """ // target C @@ -843,7 +829,7 @@ public void afterBreaksCycle3() { * Detect missing units in "after" clauses with delay greater than zero. */ @Test - public void nonzeroAfterMustHaveUnits() { + public void nonzeroAfterMustHaveUnits() throws Exception { // Java 17: // String testCase = """ // target C @@ -887,7 +873,7 @@ public void nonzeroAfterMustHaveUnits() { * Report non-zero time value without units. */ @Test - public void nonZeroTimeValueWithoutUnits() { + public void nonZeroTimeValueWithoutUnits() throws Exception { // Java 17: // String testCase = """ // target C; @@ -914,7 +900,7 @@ public void nonZeroTimeValueWithoutUnits() { * Report reference to non-time parameter in time argument. */ @Test - public void parameterTypeMismatch() { + public void parameterTypeMismatch() throws Exception { // Java 17: // String testCase = """ // target C; @@ -942,7 +928,7 @@ public void parameterTypeMismatch() { * Report inappropriate literal in time argument. */ @Test - public void targetCodeInTimeArgument() { + public void targetCodeInTimeArgument() throws Exception { // Java 17: // String testCase = """ // target C; @@ -971,7 +957,7 @@ public void targetCodeInTimeArgument() { * Report overflowing deadline. */ @Test - public void overflowingDeadlineC() { + public void overflowingDeadlineC() throws Exception { // Java 17: // String testCase = """ // target C; @@ -1003,7 +989,7 @@ public void overflowingDeadlineC() { * Report overflowing parameter. */ @Test - public void overflowingParameterC() { + public void overflowingParameterC() throws Exception { // Java 17: // String testCase = """ // target C; @@ -1037,7 +1023,7 @@ public void overflowingParameterC() { * Report overflowing assignment. */ @Test - public void overflowingAssignmentC() { + public void overflowingAssignmentC() throws Exception { // Java 17: // String testCase = """ // target C; @@ -1074,7 +1060,7 @@ public void overflowingAssignmentC() { * Report missing trigger. */ @Test - public void missingTrigger() { + public void missingTrigger() throws Exception { // Java 17: // String testCase = """ // target C; @@ -1100,7 +1086,7 @@ public void missingTrigger() { * Test warnings and errors for the target dependent preamble visibility qualifiers */ @Test - public void testPreambleVisibility() { + public void testPreambleVisibility() throws Exception { for (Target target : Target.values()) { for (Visibility visibility : Visibility.values()) { // Java 17: @@ -1175,7 +1161,7 @@ public void testPreambleVisibility() { * Tests for state and parameter declarations, including native lists. */ @Test - public void stateAndParameterDeclarationsInC() { + public void stateAndParameterDeclarationsInC() throws Exception { // Java 17: // String testCase = """ // target C; @@ -1237,7 +1223,7 @@ public void stateAndParameterDeclarationsInC() { * Recognize valid IPV4 addresses, report invalid ones. */ @Test - public void recognizeIPV4() { + public void recognizeIPV4() throws Exception { List correct = List.of("127.0.0.1", "10.0.0.1", "192.168.1.1", "0.0.0.0", "192.168.1.1"); List parseError = List.of("10002.3.4", "1.2.3.4.5"); List validationError = List.of("256.0.0.0", "260.0.0.0"); @@ -1311,7 +1297,7 @@ public void recognizeIPV4() { * Recognize valid IPV6 addresses, report invalid ones. */ @Test - public void recognizeIPV6() { + public void recognizeIPV6() throws Exception { List correct = List.of("1:2:3:4:5:6:7:8", "1:2:3:4:5:6:7::", "1:2:3:4:5:6::8", "1:2:3:4:5::8", "1:2:3:4::8", "1:2:3::8", "1:2::8", "1::8", "::8", "::", "1::3:4:5:6:7:8", "1::4:5:6:7:8", "1::5:6:7:8", "1::6:7:8", @@ -1403,7 +1389,7 @@ public void recognizeIPV6() { * Recognize valid host names and fully qualified names, report invalid ones. */ @Test - public void recognizeHostNames() { + public void recognizeHostNames() throws Exception { List correct = List.of("localhost"); // FIXME: add more @@ -1658,7 +1644,7 @@ private Model createModel(TargetProperty key, String value) { * Perform checks on target properties. */ @Test - public void checkTargetProperties() { + public void checkTargetProperties() throws Exception { for (TargetProperty prop : TargetProperty.getOptions()) { if (prop == TargetProperty.CARGO_DEPENDENCIES) { // we test that separately as it has better error messages @@ -1721,7 +1707,7 @@ public void checkTargetProperties() { @Test - public void checkCargoDependencyProperty() { + public void checkCargoDependencyProperty() throws Exception { val prop = TargetProperty.CARGO_DEPENDENCIES; val knownCorrect = #[ "{}", "{ dep: \"8.2\" }", "{ dep: { version: \"8.2\"} }", "{ dep: { version: \"8.2\", features: [\"foo\"]} }" ] knownCorrect.forEach [ From 0a8a6a46413971ec6aa3d8fb2d88c455e2df3d95 Mon Sep 17 00:00:00 2001 From: Hou Seng Wong Date: Sat, 22 Jan 2022 14:46:04 -0800 Subject: [PATCH 07/11] throw exception --- .../tests/compiler/LinguaFrancaParsingTest.java | 17 ++--------------- 1 file changed, 2 insertions(+), 15 deletions(-) diff --git a/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaParsingTest.java b/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaParsingTest.java index a50f5b669d..655c599b41 100644 --- a/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaParsingTest.java +++ b/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaParsingTest.java @@ -48,7 +48,7 @@ class LinguaFrancaParsingTest { ParseHelper parser; @Test - public void checkForTarget() { + public void checkForTarget() throws Exception { // Java 17: // String testCase = """ // targett C; @@ -61,21 +61,8 @@ public void checkForTarget() { "reactor Foo {", "}" ); - Model result = tryToParse(testCase); + Model result = parser.parse(testCase); Assertions.assertNotNull(result); Assertions.assertFalse(result.eResource().getErrors().isEmpty(), "Failed to catch misspelled target keyword.") } - - /* Helper function to try to parse a Lingua Franca program. - * @return A model representing the parsed string, or null if program cannot be parsed. - */ - private Model tryToParse(String s) { - Model model; - try { - model = parser.parse(s); - } catch (Exception e) { - model = null; - } - return model; - } } \ No newline at end of file From eac9735d29506027b6092ba84321bd17a15595d5 Mon Sep 17 00:00:00 2001 From: Hou Seng Wong Date: Sat, 22 Jan 2022 15:12:30 -0800 Subject: [PATCH 08/11] port AnalysisTest to java --- .../LinguaFrancaDependencyAnalysisTest.java | 205 ++++++++++++++++++ .../LinguaFrancaDependencyAnalysisTest.xtend | 143 ------------ 2 files changed, 205 insertions(+), 143 deletions(-) create mode 100644 org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaDependencyAnalysisTest.java delete mode 100644 org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaDependencyAnalysisTest.xtend diff --git a/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaDependencyAnalysisTest.java b/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaDependencyAnalysisTest.java new file mode 100644 index 0000000000..a8c94f54cd --- /dev/null +++ b/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaDependencyAnalysisTest.java @@ -0,0 +1,205 @@ +/* Dependency analysis unit tests. */ + +/************* +Copyright (c) 2019, The University of California at Berkeley. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +***************/ +package org.lflang.tests.compiler; + +import com.google.inject.Inject; + +import org.eclipse.emf.common.util.TreeIterator; +import org.eclipse.emf.ecore.EObject; +import org.eclipse.xtext.EOF; +import org.eclipse.xtext.testing.InjectWith; +import org.eclipse.xtext.testing.extensions.InjectionExtension; +import org.eclipse.xtext.testing.util.ParseHelper; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.lflang.DefaultErrorReporter; +import org.lflang.ModelInfo; +import org.lflang.generator.InvalidSourceException; +import org.lflang.generator.ReactionInstanceGraph; +import org.lflang.generator.ReactorInstance; +import org.lflang.lf.Instantiation; +import org.lflang.lf.LfFactory; +import org.lflang.lf.Model; +import org.lflang.lf.Reactor; +import org.lflang.tests.LFInjectorProvider; +import static org.lflang.ASTUtils.*; + +@ExtendWith(InjectionExtension.class) +@InjectWith(LFInjectorProvider.class) + +/** + * A collection of tests to ensure dependency analysis is done correctly. + * @author{Marten Lohstroh } + */ +class LinguaFrancaDependencyAnalysisTest { + @Inject + ParseHelper parser; + + /** + * Check that circular dependencies between reactions are detected. + */ + @Test + public void cyclicDependency() throws Exception { + // Java 17: +// String testCase = """ +// target C; +// +// reactor Clock { +// timer t(0, 10 msec); +// input x:int; +// output y:int; +// reaction(t) -> y {= +// +// =} +// reaction(x) -> y {= +// +// =} +// } +// +// reactor A { +// input x:int; +// output y:int; +// reaction(x) -> y {= +// +// =} +// } +// +// main reactor Loop { +// c = new Clock(); +// a = new A(); +// c.y -> a.x; +// a.y -> c.x; +// } +// """ +// Java 11: + String testCase = String.join(System.getProperty("line.separator"), + "target C;", + "", + "reactor Clock {", + " timer t(0, 10 msec);", + " input x:int;", + " output y:int;", + " reaction(t) -> y {=", + " ", + " =}", + " reaction(x) -> y {=", + " ", + " =}", + "}", + "", + "reactor A {", + " input x:int;", + " output y:int;", + " reaction(x) -> y {=", + " ", + " =}", + "}", + "", + "main reactor Loop {", + " c = new Clock();", + " a = new A();", + " c.y -> a.x;", + " a.y -> c.x;", + "}" + ); + Model model = parser.parse(testCase); + + Assertions.assertNotNull(model); + Instantiation mainDef = null; + TreeIterator it = model.eResource().getAllContents(); + while (it.hasNext()) { + EObject obj = it.next(); + if (!(obj instanceof Reactor)) { + continue; + } + Reactor reactor = (Reactor) obj; + if (reactor.isMain()) { + // Creating an definition for the main reactor because + // there isn't one. + mainDef = LfFactory.eINSTANCE.createInstantiation(); + mainDef.setName(reactor.getName()); + mainDef.setReactorClass(reactor); + } + } + + try { + ReactorInstance instance = new ReactorInstance((Reactor) mainDef.getReactorClass(), new DefaultErrorReporter()); + new ReactionInstanceGraph(instance); + Assertions.fail("No cycle detected"); + } catch(InvalidSourceException e) { + Assertions.assertTrue(e.getMessage() != null && e.getMessage().contains("Reactions form a cycle!"), + "Should be a message about cycles: " + e.getMessage()); + } + } + + /** + * Check that circular instantiations are detected. + */ + @Test + public void circularInstantiation() throws Exception { +// Java 17: +// String testCase = """ +// target C; +// +// reactor X { +// reaction() {= +// // +// =} +// p = new Y(); +// } +// +// reactor Y { +// q = new X(); +// } +// """ +// Java 11: + String testCase = String.join(System.getProperty("line.separator"), + "target C;", + "", + "reactor X {", + " reaction() {=", + " //", + " =}", + " p = new Y();", + "}", + "", + "reactor Y {", + " q = new X();", + "}" + ); + Model model = parser.parse(testCase); + + Assertions.assertNotNull(model); + + ModelInfo info = new ModelInfo(); + info.update(model, new DefaultErrorReporter()); + Assertions.assertTrue(info.instantiationGraph.hasCycles() == true, + "Did not detect cyclic instantiation."); + } + +} diff --git a/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaDependencyAnalysisTest.xtend b/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaDependencyAnalysisTest.xtend deleted file mode 100644 index 5de81cb1b2..0000000000 --- a/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaDependencyAnalysisTest.xtend +++ /dev/null @@ -1,143 +0,0 @@ -/* Dependency analysis unit tests. */ - -/************* -Copyright (c) 2019, The University of California at Berkeley. - -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR -ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON -ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -***************/ -package org.lflang.tests.compiler - -import com.google.inject.Inject -import org.eclipse.xtext.testing.InjectWith -import org.eclipse.xtext.testing.extensions.InjectionExtension -import org.eclipse.xtext.testing.util.ParseHelper -import org.junit.jupiter.api.Assertions -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.^extension.ExtendWith -import org.lflang.DefaultErrorReporter -import org.lflang.ModelInfo -import org.lflang.generator.InvalidSourceException -import org.lflang.generator.ReactionInstanceGraph -import org.lflang.generator.ReactorInstance -import org.lflang.lf.Instantiation -import org.lflang.lf.LfFactory -import org.lflang.lf.Model -import org.lflang.lf.Reactor -import org.lflang.tests.LFInjectorProvider - -import static extension org.lflang.ASTUtils.* - -@ExtendWith(InjectionExtension) -@InjectWith(LFInjectorProvider) - -/** - * A collection of tests to ensure dependency analysis is done correctly. - * @author{Marten Lohstroh } - */ -class LinguaFrancaDependencyAnalysisTest { - @Inject extension ParseHelper - - /** - * Check that circular dependencies between reactions are detected. - */ - @Test - def void cyclicDependency() { - val model = ''' - target C; - - reactor Clock { - timer t(0, 10 msec); - input x:int; - output y:int; - reaction(t) -> y {= - - =} - reaction(x) -> y {= - - =} - } - - reactor A { - input x:int; - output y:int; - reaction(x) -> y {= - - =} - } - - main reactor Loop { - c = new Clock(); - a = new A(); - c.y -> a.x; - a.y -> c.x; - } - '''.parse - - Assertions.assertNotNull(model) - var Instantiation mainDef = null; - for (reactor : model.eAllContents.filter(Reactor).toList) { - if (reactor.isMain) { - // Creating an definition for the main reactor because - // there isn't one. - mainDef = LfFactory.eINSTANCE.createInstantiation() - mainDef.setName(reactor.name) - mainDef.setReactorClass(reactor) - } - } - - try { - val instance = new ReactorInstance(mainDef.reactorClass.toDefinition, new DefaultErrorReporter()) - new ReactionInstanceGraph(instance) - Assertions.fail("No cycle detected") - } catch(InvalidSourceException e) { - Assertions.assertTrue(e.message != null && e.message.contains("Reactions form a cycle!"), - "Should be a message about cycles: " + e.message) - } - } - - /** - * Check that circular instantiations are detected. - */ - @Test - def void circularInstantiation() { - val model = '''target C; - - reactor X { - reaction() {= - // - =} - p = new Y(); - } - - reactor Y { - q = new X(); - }'''.parse - - Assertions.assertNotNull(model) - - var info = new ModelInfo() - info.update(model, new DefaultErrorReporter()) - Assertions.assertTrue(info.instantiationGraph.hasCycles == true, - "Did not detect cyclic instantiation.") - } - -} From 16437f891d20dbea639ddbb6bdc3fe56b9069e65 Mon Sep 17 00:00:00 2001 From: Hou Seng Wong Date: Mon, 24 Jan 2022 11:15:49 -0800 Subject: [PATCH 09/11] port all of validationTest --- .../compiler/LinguaFrancaParsingTest.java | 2 +- .../compiler/LinguaFrancaValidationTest.java | 147 +- .../compiler/LinguaFrancaValidationTest.xtend | 1202 ----------------- 3 files changed, 73 insertions(+), 1278 deletions(-) delete mode 100644 org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaValidationTest.xtend diff --git a/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaParsingTest.java b/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaParsingTest.java index 655c599b41..f898b76c08 100644 --- a/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaParsingTest.java +++ b/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaParsingTest.java @@ -63,6 +63,6 @@ public void checkForTarget() throws Exception { ); Model result = parser.parse(testCase); Assertions.assertNotNull(result); - Assertions.assertFalse(result.eResource().getErrors().isEmpty(), "Failed to catch misspelled target keyword.") + Assertions.assertFalse(result.eResource().getErrors().isEmpty(), "Failed to catch misspelled target keyword."); } } \ No newline at end of file diff --git a/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaValidationTest.java b/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaValidationTest.java index 2801932889..d7d5d648f5 100644 --- a/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaValidationTest.java +++ b/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaValidationTest.java @@ -1544,27 +1544,23 @@ private List synthesizeExamples(ArrayType type, boolean correct) { * depending on the value of the second parameter. */ private List synthesizeExamples(UnionType type, boolean correct) { - // val examples = newLinkedList - // if (correct) { - // type.options.forEach [ - // if (it instanceof TargetPropertyType) { - // synthesizeExamples(it, correct).forEach [ - // examples.add(it) - // ] - // } else { - // // It must be a plain Enum - // examples.add(it.toString()) - // } - // ] - // } else { - // // Return some obviously bad examples for the common - // // case where the options are from an ordinary Enum. - // if (!type.options.exists[it instanceof TargetPropertyType]) { - // return #["foo", "\"bar\"", "1", "-1", - // "{x: 42}", "[1, 2, 3]"] - // } - // } - // return examples + List examples = new LinkedList<>(); + if (correct) { + for (Enum it : type.options) { + if (it instanceof TargetPropertyType) { + synthesizeExamples((TargetPropertyType) it, correct).forEach(ex -> examples.add(ex)); + } else { + examples.add(it.toString()); + } + } + } else { + // Return some obviously bad examples for the common + // case where the options are from an ordinary Enum. + if (!type.options.stream().anyMatch(it -> (it instanceof TargetPropertyType))) { + return List.of("foo", "\"bar\"", "1", "-1", "{x: 42}", "[1, 2, 3]"); + } + } + return examples; } /** @@ -1572,15 +1568,13 @@ private List synthesizeExamples(UnionType type, boolean correct) { * depending on the value of the second parameter. */ private List synthesizeExamples(DictionaryType type, boolean correct) { - // val examples = newLinkedList - // // Produce a set of singleton dictionaries. - // // If incorrect examples are wanted, garble the key. - // type.options.forEach [ option | - // synthesizeExamples(option.type, correct).forEach [ - // examples.add('''{«option»«!correct? "iamwrong"»: «it»}''') - // ] - // ] - // return examples + List examples = new LinkedList<>(); + // Produce a set of singleton dictionaries. + // If incorrect examples are wanted, garble the key. + for (DictionaryElement option : type.options) { + synthesizeExamples(option.getType(), correct).forEach(it -> examples.add("{" + option + (!correct ? "iamwrong: " : ": ") + it + "}")); + } + return examples; } /** @@ -1620,7 +1614,7 @@ private List synthesizeExamples(TargetPropertyType type, boolean correct * Create an LF program with the given key and value as a target property, * parse it, and return the resulting model. */ - private Model createModel(TargetProperty key, String value) { + private Model createModel(TargetProperty key, String value) throws Exception { String target = key.supportedBy.get(0).getDisplayName(); System.out.println(String.format("%s: %s", key, value)); // Java 17: @@ -1654,17 +1648,18 @@ public void checkTargetProperties() throws Exception { System.out.println("===="); System.out.println("Known good assignments:"); List knownCorrect = synthesizeExamples(prop.type, true); - knownCorrect.forEach [ - val model = prop.createModel(it) - model.assertNoErrors() + + for (String it : knownCorrect) { + Model model = createModel(prop, it); + validator.assertNoErrors(model); // Also make sure warnings are produced when files are not present. if (prop.type == PrimitiveType.FILE) { - model.assertWarning( - LfPackage.eINSTANCE.keyValuePair, - null, '''Could not find file: '«it.withoutQuotes»'.''') + validator.assertWarning(model, LfPackage.eINSTANCE.getKeyValuePair(), + null, String.format("Could not find file: '%s'.", withoutQuotes(it))); } - ] - // Extra checks for filenames. + } + + // Extra checks for filenames. (This piece of code was commented out in the original xtend file) // Temporarily disabled because we need a more sophisticated check that looks for files in different places. // if (prop.type == prop.type == ArrayType.FILE_ARRAY || // prop.type == UnionType.FILE_OR_FILE_ARRAY) { @@ -1677,54 +1672,56 @@ public void checkTargetProperties() throws Exception { // ] // } - println("Known bad assignments:") - val knownIncorrect = synthesizeExamples(prop.type, false) - if (!knownIncorrect.isNullOrEmpty) { - knownIncorrect.forEach [ - prop.createModel(it).assertError( - LfPackage.eINSTANCE.keyValuePair, - null, '''Target property '«prop.toString»' is required to be «prop.type».''') - ] + System.out.println("Known bad assignments:"); + List knownIncorrect = synthesizeExamples(prop.type, false); + if (!(knownIncorrect == null || knownIncorrect.isEmpty())) { + for (String it : knownIncorrect) { + validator.assertError(createModel(prop, it), + LfPackage.eINSTANCE.getKeyValuePair(), null, + String.format("Target property '%s' is required to be %s.", prop.toString(), prop.type)); + } } else { // No type was synthesized. It must be a composite type. - val list = compositeTypeToKnownBad.get(prop.type) - if (list === null) { - println('''No known incorrect values provided for target property '«prop»'. Aborting test.''') - assertTrue(false) + List> list = compositeTypeToKnownBad.get(prop.type); + if (list == null) { + System.out.println(String.format("No known incorrect values provided for target property '%s'. Aborting test.", prop)); + Assertions.assertTrue(false); } else { - list.forEach [ - prop.createModel(it.get(0).toString). - assertError( - LfPackage.eINSTANCE.keyValuePair, - null, '''Target property '«prop.toString»«it.get(1)»' is required to be «it.get(2)».''') - ] + for (List it : list) { + validator.assertError(createModel(prop, it.get(0).toString()), + LfPackage.eINSTANCE.getKeyValuePair(), null, + String.format("Target property '%s%s' is required to be %s.", prop.toString(), it.get(1), it.get(2))); + } } - } - println("====") + } + System.out.println("===="); } - println("Done!") + System.out.println("Done!"); } @Test public void checkCargoDependencyProperty() throws Exception { - val prop = TargetProperty.CARGO_DEPENDENCIES; - val knownCorrect = #[ "{}", "{ dep: \"8.2\" }", "{ dep: { version: \"8.2\"} }", "{ dep: { version: \"8.2\", features: [\"foo\"]} }" ] - knownCorrect.forEach [ - prop.createModel(it).assertNoErrors() - ] - - // vvvvvvvvvvv - prop.createModel("{ dep: {/*empty*/} }") - .assertError(LfPackage.eINSTANCE.keyValuePairs, null, "Must specify one of 'version', 'path', or 'git'") + TargetProperty prop = TargetProperty.CARGO_DEPENDENCIES; + List knownCorrect = List.of("{}", "{ dep: \"8.2\" }", "{ dep: { version: \"8.2\"} }", "{ dep: { version: \"8.2\", features: [\"foo\"]} }"); + for (String it : knownCorrect) { + validator.assertNoErrors(createModel(prop, it)); + } - // vvvvvvvvvvv - prop.createModel("{ dep: { unknown_key: \"\"} }") - .assertError(LfPackage.eINSTANCE.keyValuePair, null, "Unknown key: 'unknown_key'") + // vvvvvvvvvvv + validator.assertError(createModel(prop, "{ dep: {/*empty*/} }"), + LfPackage.eINSTANCE.getKeyValuePairs(), null, "Must specify one of 'version', 'path', or 'git'" + ); + + // vvvvvvvvvvv + validator.assertError(createModel(prop, "{ dep: { unknown_key: \"\"} }"), + LfPackage.eINSTANCE.getKeyValuePair(), null, "Unknown key: 'unknown_key'" + ); - // vvvv - prop.createModel("{ dep: { features: \"\" } }") - .assertError(LfPackage.eINSTANCE.element, null, "Expected an array of strings for key 'features'") + // vvvv + validator.assertError(createModel(prop, "{ dep: { features: \"\" } }"), + LfPackage.eINSTANCE.getElement(), null, "Expected an array of strings for key 'features'" + ); } } diff --git a/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaValidationTest.xtend b/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaValidationTest.xtend deleted file mode 100644 index 77d34ed54f..0000000000 --- a/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaValidationTest.xtend +++ /dev/null @@ -1,1202 +0,0 @@ -/* Scoping unit tests. */ - -/************* -Copyright (c) 2019, The University of California at Berkeley. - -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR -ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON -ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -***************/ -package org.lflang.tests.compiler - -import com.google.inject.Inject -import java.util.List -import org.eclipse.xtext.testing.InjectWith -import org.eclipse.xtext.testing.extensions.InjectionExtension -import org.eclipse.xtext.testing.util.ParseHelper -import org.eclipse.xtext.testing.validation.ValidationTestHelper -import org.lflang.Target -import org.lflang.TargetProperty -import org.lflang.TargetProperty.DictionaryType -import org.lflang.TargetProperty.PrimitiveType -import org.lflang.TargetProperty.TargetPropertyType -import org.lflang.TimeValue -import org.lflang.lf.LfPackage -import org.lflang.lf.Model -import org.lflang.lf.Visibility -import org.junit.jupiter.api.Assertions -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.^extension.ExtendWith - -import static org.junit.jupiter.api.Assertions.assertNotNull -import static org.junit.jupiter.api.Assertions.assertTrue -import static org.junit.jupiter.api.Assertions.fail - -import static extension org.lflang.ASTUtils.* -import org.lflang.TargetProperty.UnionType -import org.lflang.TargetProperty.ArrayType -import org.lflang.tests.LFInjectorProvider - -@ExtendWith(InjectionExtension) -@InjectWith(LFInjectorProvider) -/** - * Collection of unit tests to ensure validation is done correctly. - * - * @author{Edward A. Lee } - * @author{Marten Lohstroh } - * @author{Matt Weber } - * @author(Christian Menard } - */ -class LinguaFrancaValidationTest { - @Inject extension ParseHelper - @Inject extension ValidationTestHelper - - /** - * Helper function to parse a Lingua Franca program and expect no errors. - * @return A model representing the parsed string. - */ - def parseWithoutError(String s) { - val model = s.parse - Assertions.assertNotNull(model) - Assertions.assertTrue(model.eResource.errors.isEmpty, - "Encountered unexpected error while parsing: " + - model.eResource.errors) - return model - } - - /** - * Helper function to parse a Lingua Franca program and expect errors. - * @return A model representing the parsed string. - */ - def parseWithError(String s) { - val model = s.parse - Assertions.assertNotNull(model) - Assertions.assertFalse(model.eResource.errors.isEmpty) - return model - } - - /** - * Ensure that duplicate identifiers for actions reported. - */ - @Test - def void duplicateVariable() { - parseWithoutError(''' - target TypeScript; - main reactor Foo { - logical action bar; - physical action bar; - } - ''').assertError(LfPackage::eINSTANCE.action, null, - "Duplicate Variable 'bar' in Reactor 'Foo'") - } - - /** - * Check that reactors in C++ cannot be named preamble - */ - @Test - def void disallowReactorCalledPreamble() { - val model_no_errors = ''' - target Cpp; - main reactor { - } - '''.parse - - val model_error_1 = ''' - target Cpp; - main reactor Preamble { - } - '''.parse - - val model_error_2 = ''' - target Cpp; - reactor Preamble { - } - main reactor Main { - } - '''.parse - - Assertions.assertNotNull(model_no_errors) - Assertions.assertNotNull(model_error_1) - Assertions.assertNotNull(model_error_2) - Assertions.assertTrue(model_no_errors.eResource.errors.isEmpty, - "Encountered unexpected error while parsing: " + model_no_errors.eResource.errors) - Assertions.assertTrue(model_error_1.eResource.errors.isEmpty, - "Encountered unexpected error while parsing: " + model_error_1.eResource.errors) - Assertions.assertTrue(model_error_2.eResource.errors.isEmpty, - "Encountered unexpected error while parsing: " + model_error_2.eResource.errors) - - model_no_errors.assertNoIssues - model_error_1.assertError(LfPackage::eINSTANCE.reactor, null, - "Reactor cannot be named 'Preamble'") - model_error_2.assertError(LfPackage::eINSTANCE.reactor, null, - "Reactor cannot be named 'Preamble'") - } - - /** - * Ensure that "__" is not allowed at the start of an input name. - */ - @Test - def void disallowUnderscoreInputs() { - parseWithoutError(''' - target TypeScript; - main reactor { - input __bar; - } - ''').assertError(LfPackage::eINSTANCE.input, null, - "Names of objects (inputs, outputs, actions, timers, parameters, state, reactor definitions, and reactor instantiation) may not start with \"__\": __bar") - } - - @Test - def void disallowMainWithDifferentNameThanFile() { - parseWithoutError(''' - target C; - main reactor Foo {} - ''').assertError(LfPackage::eINSTANCE.reactor, null, - "Name of main reactor must match the file name (or be omitted)") - } - - - /** - * Ensure that "__" is not allowed at the start of an output name. - */ - @Test - def void disallowUnderscoreOutputs() { - parseWithoutError(''' - target TypeScript; - main reactor Foo { - output __bar; - } - ''').assertError(LfPackage::eINSTANCE.output, null, - "Names of objects (inputs, outputs, actions, timers, parameters, state, reactor definitions, and reactor instantiation) may not start with \"__\": __bar") - } - - /** - * Ensure that "__" is not allowed at the start of an action name. - */ - @Test - def void disallowUnderscoreActions() { - parseWithoutError(''' - target TypeScript; - main reactor Foo { - logical action __bar; - } - ''').assertError(LfPackage::eINSTANCE.action, null, - "Names of objects (inputs, outputs, actions, timers, parameters, state, reactor definitions, and reactor instantiation) may not start with \"__\": __bar") - } - - /** - * Ensure that "__" is not allowed at the start of a timer name. - */ - @Test - def void disallowUnderscoreTimers() { - parseWithoutError(''' - target TypeScript; - main reactor Foo { - timer __bar(0); - } - ''').assertError(LfPackage::eINSTANCE.timer, null, - "Names of objects (inputs, outputs, actions, timers, parameters, state, reactor definitions, and reactor instantiation) may not start with \"__\": __bar") - } - - /** - * Ensure that "__" is not allowed at the start of a parameter name. - */ - @Test - def void disallowUnderscoreParameters() { - parseWithoutError(''' - target TypeScript; - main reactor Foo(__bar) { - } - ''').assertError(LfPackage::eINSTANCE.parameter, null, - "Names of objects (inputs, outputs, actions, timers, parameters, state, reactor definitions, and reactor instantiation) may not start with \"__\": __bar") - } - - /** - * Ensure that "__" is not allowed at the start of an state name. - */ - @Test - def void disallowUnderscoreStates() { - parseWithoutError(''' - target TypeScript; - main reactor Foo { - state __bar; - } - ''').assertError(LfPackage::eINSTANCE.stateVar, null, - "Names of objects (inputs, outputs, actions, timers, parameters, state, reactor definitions, and reactor instantiation) may not start with \"__\": __bar") - } - - /** - * Ensure that "__" is not allowed at the start of a reactor definition name. - */ - @Test - def void disallowUnderscoreReactorDef() { - parseWithoutError(''' - target TypeScript; - main reactor __Foo { - } - ''').assertError(LfPackage::eINSTANCE.reactor, null, - "Names of objects (inputs, outputs, actions, timers, parameters, state, reactor definitions, and reactor instantiation) may not start with \"__\": __Foo") - } - - /** - * Ensure that "__" is not allowed at the start of a reactor instantiation name. - */ - @Test - def void disallowUnderscoreReactorInstantiation() { - parseWithoutError(''' - target TypeScript; - reactor Foo { - } - main reactor Bar { - __x = new Foo(); - } - ''').assertError(LfPackage::eINSTANCE.instantiation, null, - "Names of objects (inputs, outputs, actions, timers, parameters, state, reactor definitions, and reactor instantiation) may not start with \"__\": __x") - } - - /** - * Disallow connection to port that is effect of reaction. - */ - @Test - def void connectionToEffectPort() { - parseWithoutError(''' - target C; - reactor Foo { - output out:int; - } - main reactor Bar { - output out:int; - x = new Foo(); - x.out -> out; - reaction(startup) -> out {= - =} - } - ''').assertError(LfPackage::eINSTANCE.connection, null, - "Cannot connect: Port named 'out' is already effect of a reaction.") - } - - /** - * Disallow connection to port that is effect of reaction. - */ - @Test - def void connectionToEffectPort2() { - parseWithoutError(''' - target C; - reactor Foo { - input inp:int; - output out:int; - } - main reactor { - output out:int; - x = new Foo(); - y = new Foo(); - - y.out -> x.inp; - reaction(startup) -> x.inp {= - =} - } - ''').assertError(LfPackage::eINSTANCE.connection, null, - "Cannot connect: Port named 'inp' is already effect of a reaction.") - } - - /** - * Allow connection to the port of a contained reactor if another port with same name is effect of a reaction. - */ - @Test - def void connectionToEffectPort3() { - parseWithoutError(''' - target C; - - reactor Foo { - input in:int; - } - main reactor { - input in:int; - x1 = new Foo(); - x2 = new Foo(); - in -> x1.in; - reaction(startup) -> x2.in {= - =} - } - ''').assertNoErrors() - } - - /** - * Disallow connection to the port of a contained reactor if the same port is effect of a reaction. - */ - @Test - def void connectionToEffectPort4() { - parseWithoutError(''' - target C; - - reactor Foo { - input in:int; - } - main reactor { - input in:int; - x1 = new Foo(); - in -> x1.in; - reaction(startup) -> x1.in {= - =} - } - ''').assertError(LfPackage::eINSTANCE.connection, null, - "Cannot connect: Port named 'in' is already effect of a reaction.") - } - - /** - * Disallow connection of multiple ports to the same input port. - */ - @Test - def void multipleConnectionsToInputTest() { - parseWithoutError(''' - target C; - - reactor Source { - output out:int; - } - reactor Sink { - input in:int; - } - - main reactor { - input in:int; - src = new Source(); - sink = new Sink(); - - in -> sink.in; - src.out -> sink.in; - } - ''').assertError(LfPackage::eINSTANCE.connection, null, - "Cannot connect: Port named 'in' may only appear once on the right side of a connection.") - } - - /** - * Detect cycles in the instantiation graph. - */ - @Test - def void detectInstantiationCycle() { - parseWithoutError(''' - target C; - - reactor Contained { - x = new Contained(); - } - ''').assertError(LfPackage::eINSTANCE.instantiation, - null, 'Instantiation is part of a cycle: Contained') - } - - - /** - * Detect cycles in the instantiation graph. - */ - @Test - def void detectInstantiationCycle2() { - val model = parseWithoutError(''' - target C; - reactor Intermediate { - x = new Contained(); - } - - reactor Contained { - x = new Intermediate(); - } - ''') - model.assertError(LfPackage::eINSTANCE.instantiation, - null, 'Instantiation is part of a cycle: Intermediate, Contained.') - model.assertError(LfPackage::eINSTANCE.instantiation, - null, 'Instantiation is part of a cycle: Intermediate, Contained.') - } - - /** - * Detect causality loop. - */ - @Test - def void detectCausalityLoop() { - val model = parseWithoutError(''' - target C - - reactor X { - input x:int; - output y:int; - reaction(x) -> y {= - =} - } - - main reactor { - a = new X() - b = new X() - a.y -> b.x - b.y -> a.x - } - - ''') - model.assertError(LfPackage::eINSTANCE.reaction, - null, 'Reaction triggers involved in cyclic dependency in reactor X: x.') - model.assertError(LfPackage::eINSTANCE.reaction, - null, 'Reaction effects involved in cyclic dependency in reactor X: y.') - - } - - /** - * Let cyclic dependencies be broken by "after" clauses. - */ - @Test - def void afterBreaksCycle() { - parseWithoutError(''' - target C - - reactor X { - input x:int; - output y:int; - reaction(x) -> y {= - =} - } - - main reactor { - a = new X() - b = new X() - a.y -> b.x after 5 msec - b.y -> a.x - } - - ''').assertNoErrors() - - } - - - /** - * Let cyclic dependencies be broken by "after" clauses with zero delay. - */ - @Test - def void afterBreaksCycle2() { - parseWithoutError(''' - target C - - reactor X { - input x:int; - output y:int; - reaction(x) -> y {= - =} - } - - main reactor { - a = new X() - b = new X() - a.y -> b.x after 0 sec - b.y -> a.x - } - - ''').assertNoErrors() - - } - - - /** - * Let cyclic dependencies be broken by "after" clauses with zero delay and no units. - */ - @Test - def void afterBreaksCycle3() { - parseWithoutError(''' - target C - - reactor X { - input x:int; - output y:int; - reaction(x) -> y {= - =} - } - - main reactor { - a = new X() - b = new X() - a.y -> b.x after 0 - b.y -> a.x - } - - ''').assertNoErrors() - - } - - /** - * Detect missing units in "after" clauses with delay greater than zero. - */ - @Test - def void nonzeroAfterMustHaveUnits() { - parseWithoutError(''' - target C - - reactor X { - input x:int; - output y:int; - reaction(x) -> y {= - =} - } - - main reactor { - a = new X() - b = new X() - a.y -> b.x after 1 - } - - ''').assertError(LfPackage::eINSTANCE.time, - null, 'Missing time unit.') - - } - - - - /** - * Report non-zero time value without units. - */ - @Test - def void nonZeroTimeValueWithoutUnits() { - parseWithoutError(''' - target C; - main reactor { - timer t(42, 1 sec); - reaction(t) {= - printf("Hello World.\n"); - =} - } - ''').assertError(LfPackage::eINSTANCE.value, null, "Missing time unit.") - } - - /** - * Report reference to non-time parameter in time argument. - */ - @Test - def void parameterTypeMismatch() { - parseWithoutError(''' - target C; - main reactor (p:int(0)) { - timer t(p, 1 sec); - reaction(t) {= - printf("Hello World.\n"); - =} - } - ''').assertError(LfPackage::eINSTANCE.value, - null, 'Parameter is not of time type') - - } - - /** - * Report inappropriate literal in time argument. - */ - @Test - def void targetCodeInTimeArgument() { - parseWithoutError(''' - target C; - main reactor { - timer t({=foo()=}, 1 sec); - reaction(t) {= - printf("Hello World.\n"); - =} - } - ''').assertError(LfPackage::eINSTANCE.value, - null, 'Invalid time literal') - } - - - /** - * Report overflowing deadline. - */ - @Test - def void overflowingDeadlineC() { - parseWithoutError(''' - target C; - main reactor { - timer t; - reaction(t) {= - printf("Hello World.\n"); - =} deadline (40 hours) {= - =} - } - ''').assertError(LfPackage::eINSTANCE.deadline, null, - "Deadline exceeds the maximum of " + TimeValue.MAX_LONG_DEADLINE + - " nanoseconds.") - } - - - /** - * Report overflowing parameter. - */ - @Test - def void overflowingParameterC() { - parseWithoutError(''' - target C; - main reactor(d:time(40 hours)) { - timer t; - reaction(t) {= - printf("Hello World.\n"); - =} deadline (d) {= - =} - } - ''').assertError(LfPackage::eINSTANCE.parameter, null, - "Time value used to specify a deadline exceeds the maximum of " + - TimeValue.MAX_LONG_DEADLINE + " nanoseconds.") - } - - - /** - * Report overflowing assignment. - */ - @Test - def void overflowingAssignmentC() { - parseWithoutError(''' - target C; - reactor Print(d:time(39 hours)) { - timer t; - reaction(t) {= - printf("Hello World.\n"); - =} deadline (d) {= - =} - } - main reactor { - p = new Print(d=40 hours); - } - ''').assertError(LfPackage::eINSTANCE.assignment, null, - "Time value used to specify a deadline exceeds the maximum of " + - TimeValue.MAX_LONG_DEADLINE + " nanoseconds.") - } - - /** - * Report missing trigger. - */ - @Test - def void missingTrigger() { - parseWithoutError(''' - target C; - reactor X { - reaction() {= - // - =} - } - ''').assertWarning(LfPackage::eINSTANCE.reaction, null, - "Reaction has no trigger.") - } - - /** - * Test warnings and errors for the target dependent preamble visibility qualifiers - */ - @Test - def void testPreambleVisibility() { - for (target : Target.values) { - for (visibility : Visibility.values) { - val model_reactor_scope = parseWithoutError(''' - target «target»; - reactor Foo { - «IF visibility != Visibility.NONE»«visibility» «ENDIF»preamble {==} - } - ''') - - val model_file_scope = parseWithoutError(''' - target «target»; - «IF visibility != Visibility.NONE»«visibility» «ENDIF»preamble {==} - reactor Foo { - } - ''') - - val model_no_preamble = parseWithoutError(''' - target «target»; - reactor Foo { - } - ''') - - model_no_preamble.assertNoIssues - - if (target == Target.CPP) { - if (visibility == Visibility.NONE) { - model_file_scope.assertError(LfPackage::eINSTANCE.preamble, null, - "Preambles for the C++ target need a visibility qualifier (private or public)!") - model_reactor_scope.assertError(LfPackage::eINSTANCE.preamble, null, - "Preambles for the C++ target need a visibility qualifier (private or public)!") - } else { - model_file_scope.assertNoIssues - model_reactor_scope.assertNoIssues - } - } else { - if (visibility == Visibility.NONE) { - model_file_scope.assertNoIssues - model_reactor_scope.assertNoIssues - } else { - model_file_scope.assertWarning(LfPackage::eINSTANCE.preamble, null, - '''The «visibility» qualifier has no meaning for the «target.name» target. It should be removed.''') - model_reactor_scope.assertWarning(LfPackage::eINSTANCE.preamble, null, - '''The «visibility» qualifier has no meaning for the «target.name» target. It should be removed.''') - } - } - } - } - } - - - /** - * Tests for state and parameter declarations, including native lists. - */ - @Test - def void stateAndParameterDeclarationsInC() { - val model = parseWithoutError(''' - target C; - reactor Bar(a(0), // ERROR: type missing - b:int, // ERROR: uninitialized - t:time(42), // ERROR: units missing - x:int(0), - h:time("bla"), // ERROR: not a type - q:time(1 msec, 2 msec), // ERROR: not a list - y:int(t) // ERROR: init using parameter - ) { - state offset:time(42); // ERROR: units missing - state w:time(x); // ERROR: parameter is not a time - state foo:time("bla"); // ERROR: assigned value not a time - timer tick(1); // ERROR: not a time - } - ''') - - model.assertError(LfPackage::eINSTANCE.parameter, null, - "Type declaration missing.") - model.assertError(LfPackage::eINSTANCE.parameter, null, - "Missing time unit.") - model.assertError(LfPackage::eINSTANCE.parameter, null, - "Invalid time literal.") - model.assertError(LfPackage::eINSTANCE.parameter, null, - "Time parameter cannot be initialized using a list.") - model.assertError(LfPackage::eINSTANCE.parameter, null, - "Parameter cannot be initialized using parameter.") - model.assertError(LfPackage::eINSTANCE.stateVar, null, - "Referenced parameter does not denote a time.") - model.assertError(LfPackage::eINSTANCE.stateVar, null, - "Invalid time literal.") - model.assertError(LfPackage::eINSTANCE.parameter, null, - "Uninitialized parameter.") - model.assertError(LfPackage::eINSTANCE.value, null, - "Missing time unit.") - } - - - /** - * Recognize valid IPV4 addresses, report invalid ones. - */ - @Test - def void recognizeIPV4() { - - val correct = #["127.0.0.1", "10.0.0.1", "192.168.1.1", "0.0.0.0", - "192.168.1.1"] - val parseError = #["10002.3.4", "1.2.3.4.5"] - val validationError = #["256.0.0.0", "260.0.0.0"] - - // Correct IP addresses. - correct.forEach [ addr | - parseWithoutError(''' - target C; - reactor Y {} - federated reactor X at foo@«addr»:4242 { - y = new Y() at «addr»:2424; - } - ''') - ] - - // IP addresses that don't parse. - parseError.forEach [ addr | - parseWithError(''' - target C; - reactor Y {} - federated reactor X at foo@«addr»:4242 { - y = new Y() at «addr»:2424; - } - ''') - ] - - // IP addresses that parse but are invalid. - validationError.forEach [ addr | - parseWithoutError(''' - target C; - reactor Y {} - federated reactor X at foo@«addr»:4242 { - y = new Y() at «addr»:2424; - } - ''').assertWarning(LfPackage::eINSTANCE.host, null, - "Invalid IP address.") - ] - } - - /** - * Recognize valid IPV6 addresses, report invalid ones. - */ - @Test - def void recognizeIPV6() { - - val correct = #["1:2:3:4:5:6:7:8", "1:2:3:4:5:6:7::", "1:2:3:4:5:6::8", - "1:2:3:4:5::8", "1:2:3:4::8", "1:2:3::8", "1:2::8", "1::8", "::8", - "::", "1::3:4:5:6:7:8", "1::4:5:6:7:8", "1::5:6:7:8", "1::6:7:8", - "1::7:8", "1::8", "1::", "1:2:3:4:5::7:8", "1:2:3:4::6:7:8", - "1:2:3::5:6:7:8", "1:2::4:5:6:7:8", "1::3:4:5:6:7:8", - "::2:3:4:5:6:7:8", "fe80::7:8", "fe80::7:8%eth0", "fe80::7:8%1", - "::255.255.255.255", "::ffff:255.255.255.255", - "::ffff:0:255.255.255.0", "::ffff:00:255.255.255.0", - "::ffff:000:255.255.255.0", "::ffff:0000:255.255.255.0", - "::ffff:0.0.0.0", "::ffff:1.2.3.4", "::ffff:10.0.0.1", - "1:2:3:4:5:6:77:88", "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", - "2001:db8:3:4::192.0.2.33", "64:ff9b::192.0.2.33", "0:0:0:0:0:0:10.0.0.1"] - - val validationError = #["1:2:3:4:5:6:7:8:9", "1:2:3:4:5:6::7:8", - "1:2:3:4:5:6:7:8:", "::1:2:3:4:5:6:7:8", "1:2:3:4:5:6:7:8::", - "1:2:3:4:5:6:7:88888", "2001:db8:3:4:5::192.0.2.33", - "fe08::7:8interface", "fe08::7:8interface", "fe08::7:8i"] - - val parseError = #["fe08::7:8%", ":1:2:3:4:5:6:7:8"] - - // Correct IP addresses. - correct.forEach [ addr | - parseWithoutError(''' - target C; - reactor Y {} - federated reactor at [foo@«addr»]:4242 { - y = new Y() at [«addr»]:2424; - } - ''').assertNoIssues() - ] - - // IP addresses that don't parse. - parseError.forEach [ addr | - parseWithError(''' - target C; - reactor Y {} - federated reactor at [foo@«addr»]:4242 { - y = new Y() at [«addr»]:2424; - } - ''') - ] - - // IP addresses that parse but are invalid. - validationError.forEach [ addr | - parseWithoutError(''' - target C; - reactor Y {} - federated reactor at [foo@«addr»]:4242 { - y = new Y() at [«addr»]:2424; - } - ''').assertWarning(LfPackage::eINSTANCE.host, null, - "Invalid IP address.") - ] - } - - /** - * Recognize valid host names and fully qualified names, report invalid ones. - */ - @Test - def void recognizeHostNames() { - - val correct = #["localhost"] // FIXME: add more - - val validationError = #["x.y.z"] // FIXME: add more - - val parseError = #["..xyz"] // FIXME: add more - - // Correct names. - correct.forEach [ addr | - parseWithoutError(''' - target C; - reactor Y {} - federated reactor at foo@«addr»:4242 { - y = new Y() at «addr»:2424; - } - ''').assertNoIssues() - ] - - // Names that don't parse. - parseError.forEach [ addr | - parseWithError(''' - target C; - reactor Y {} - federated reactor at foo@«addr»:4242 { - y = new Y() at «addr»:2424; - } - ''') - ] - - // Names that parse but are invalid. - validationError.forEach [ addr | - parseWithoutError(''' - target C; - reactor Y {} - federated reactor at foo@«addr»:4242 { - y = new Y() at «addr»:2424; - } - ''').assertWarning(LfPackage::eINSTANCE.host, null, - "Invalid host name or fully qualified domain name.") - ] - } - - /** - * Maps a type to a list of known good values. - */ - val primitiveTypeToKnownGood = #{ - PrimitiveType.BOOLEAN -> #["true", "\"true\"", "false", "\"false\""], - PrimitiveType.INTEGER -> #["0", "1", "\"42\"", "\"-1\"", "-2"], - PrimitiveType.NON_NEGATIVE_INTEGER -> #["0", "1", "42"], - PrimitiveType.TIME_VALUE -> #["1 msec", "2 sec"], - PrimitiveType.STRING -> #["1", "\"foo\"", "bar"], - PrimitiveType.FILE -> #["valid.file", "something.json", "\"foobar.proto\""] - } - - /** - * Maps a type to a list of known bad values. - */ - val primitiveTypeToKnownBad = #{ - PrimitiveType.BOOLEAN -> #["1 sec", "foo", "\"foo\"", "[1]", "{baz: 42}", "'c'"], - PrimitiveType.INTEGER -> #["foo", "\"bar\"", "1 sec", "[1, 2]", "{foo: \"bar\"}", "'c'"], - PrimitiveType.NON_NEGATIVE_INTEGER -> #["-42", "foo", "\"bar\"", "1 sec", "[1, 2]", "{foo: \"bar\"}", "'c'"], - PrimitiveType.TIME_VALUE -> #["foo", "\"bar\"", "\"3 sec\"", "\"4 weeks\"", "[1, 2]", "{foo: \"bar\"}", "'c'"], - PrimitiveType.STRING -> #["1 msec", "[1, 2]", "{foo: \"bar\"}", "'c'"] - } - - /** - * Maps a type to a list, each entry of which represents a list with - * three entries: a known wrong value, the suffix to add to the reported - * name, and the type that it should be. - */ - val compositeTypeToKnownBad = #{ - ArrayType.STRING_ARRAY -> #[ - #["[1 msec]", "[0]", PrimitiveType.STRING], - #["[foo, {bar: baz}]", "[1]", PrimitiveType.STRING], - #["{bar: baz}", "", ArrayType.STRING_ARRAY] - ], - UnionType.STRING_OR_STRING_ARRAY -> #[ - #["[1 msec]", "[0]", PrimitiveType.STRING], - #["[foo, {bar: baz}]", "[1]", PrimitiveType.STRING], - #["{bar: baz}", "", UnionType.STRING_OR_STRING_ARRAY] - ], - UnionType.FILE_OR_FILE_ARRAY -> #[ - #["[1 msec]", "[0]", PrimitiveType.FILE], - #["[foo, {bar: baz}]", "[1]", PrimitiveType.FILE], - #["{bar: baz}", "", UnionType.FILE_OR_FILE_ARRAY] - ], - UnionType.DOCKER_UNION -> #[ - #["foo", "", UnionType.DOCKER_UNION], - #["[1]", "", UnionType.DOCKER_UNION], - #["{bar: baz}", "", DictionaryType.DOCKER_DICT], - #["{FROM: [1, 2, 3]}", ".FROM", PrimitiveType.STRING] - ], - UnionType.TRACING_UNION -> #[ - #["foo", "", UnionType.TRACING_UNION], - #["[1]", "", UnionType.TRACING_UNION], - #["{bar: baz}", "", DictionaryType.TRACING_DICT], - #["{trace-file-name: [1, 2, 3]}", ".trace-file-name", PrimitiveType.STRING] - ] - } - - /** - * Given an array type, return a list of good or bad examples, - * depending on the value of the second parameter. - */ - def List synthesizeExamples(ArrayType type, boolean correct) { - val values = correct ? primitiveTypeToKnownGood : primitiveTypeToKnownBad - val examples = newLinkedList - if (correct) { - // Produce an array that has an entry for each value. - val entries = values.get(type.type) - if (!entries.nullOrEmpty) { - examples.add('''[«entries.join(', ')»]''') - } - } - return examples - } - - /** - * Given an union type, return a list of good or bad examples, - * depending on the value of the second parameter. - */ - def List synthesizeExamples(UnionType type, boolean correct) { - val examples = newLinkedList - if (correct) { - type.options.forEach [ - if (it instanceof TargetPropertyType) { - synthesizeExamples(it, correct).forEach [ - examples.add(it) - ] - } else { - // It must be a plain Enum - examples.add(it.toString()) - } - ] - } else { - // Return some obviously bad examples for the common - // case where the options are from an ordinary Enum. - if (!type.options.exists[it instanceof TargetPropertyType]) { - return #["foo", "\"bar\"", "1", "-1", - "{x: 42}", "[1, 2, 3]"] - } - } - return examples - } - - /** - * Given an union type, return a list of good or bad examples, - * depending on the value of the second parameter. - */ - def List synthesizeExamples(DictionaryType type, boolean correct) { - val examples = newLinkedList - // Produce a set of singleton dictionaries. - // If incorrect examples are wanted, garble the key. - type.options.forEach [ option | - synthesizeExamples(option.type, correct).forEach [ - examples.add('''{«option»«!correct? "iamwrong"»: «it»}''') - ] - ] - return examples - } - - /** - * Synthesize a list of values that either conform to the given type or - * do not, depending on whether the second argument 'correct' is true. - * Return an empty set if it is too complicated to generate examples - * (e.g., because the resulting errors are more sophisticated). - * - * Not all cases are covered by this function. Currently, the only cases not - * covered are known bad examples for composite types, which should be added - * to the compositeTypeToKnownBad map. - * - * @param correct True to synthesize correct examples automatically, otherwise - * synthesize incorrect examples. - */ - def List synthesizeExamples(TargetPropertyType type, boolean correct) { - if (type instanceof PrimitiveType) { - val values = correct ? primitiveTypeToKnownGood : primitiveTypeToKnownBad - val examples = values.get(type).toList - assertNotNull(examples) - return examples - } else { - if (type instanceof UnionType) { - return synthesizeExamples(type, correct) - } else if (type instanceof ArrayType) { - return synthesizeExamples(type, correct) - } else if (type instanceof DictionaryType) { - return synthesizeExamples(type, correct) - } else { - fail("Encountered an unknown type: " + type) - } - } - return #[] - } - - /** - * Create an LF program with the given key and value as a target property, - * parse it, and return the resulting model. - */ - def createModel(TargetProperty key, String value) { - val target = key.supportedBy.get(0).displayName - println('''«key»: «value»''') - return parseWithoutError(''' - target «target» {«key»: «value»}; - reactor Y {} - main reactor { - y = new Y() - } - ''') - } - - /** - * Perform checks on target properties. - */ - @Test - def void checkTargetProperties() { - - for (prop : TargetProperty.options) { - if (prop == TargetProperty.CARGO_DEPENDENCIES) { - // we test that separately as it has better error messages - return - } - println('''Testing target property «prop» which is «prop.type»''') - println("====") - println("Known good assignments:") - val knownCorrect = synthesizeExamples(prop.type, true) - knownCorrect.forEach [ - val model = prop.createModel(it) - model.assertNoErrors() - // Also make sure warnings are produced when files are not present. - if (prop.type == PrimitiveType.FILE) { - model.assertWarning( - LfPackage::eINSTANCE.keyValuePair, - null, '''Could not find file: '«it.withoutQuotes»'.''') - } - ] - // Extra checks for filenames. - // Temporarily disabled because we need a more sophisticated check that looks for files in different places. -// if (prop.type == prop.type == ArrayType.FILE_ARRAY || -// prop.type == UnionType.FILE_OR_FILE_ARRAY) { -// val model = prop.createModel( -// synthesizeExamples(ArrayType.FILE_ARRAY, true).get(0)) -// primitiveTypeToKnownGood.get(PrimitiveType.FILE).forEach [ -// model.assertWarning( -// LfPackage::eINSTANCE.keyValuePair, -// null, '''Could not find file: '«it.withoutQuotes»'.''') -// ] -// } - - println("Known bad assignments:") - val knownIncorrect = synthesizeExamples(prop.type, false) - if (!knownIncorrect.isNullOrEmpty) { - knownIncorrect.forEach [ - prop.createModel(it).assertError( - LfPackage::eINSTANCE.keyValuePair, - null, '''Target property '«prop.toString»' is required to be «prop.type».''') - ] - } else { - // No type was synthesized. It must be a composite type. - val list = compositeTypeToKnownBad.get(prop.type) - if (list === null) { - println('''No known incorrect values provided for target property '«prop»'. Aborting test.''') - assertTrue(false) - } else { - list.forEach [ - prop.createModel(it.get(0).toString). - assertError( - LfPackage::eINSTANCE.keyValuePair, - null, '''Target property '«prop.toString»«it.get(1)»' is required to be «it.get(2)».''') - ] - } - } - println("====") - } - println("Done!") - } - - - @Test - def void checkCargoDependencyProperty() { - val prop = TargetProperty.CARGO_DEPENDENCIES - val knownCorrect = #[ "{}", "{ dep: \"8.2\" }", "{ dep: { version: \"8.2\"} }", "{ dep: { version: \"8.2\", features: [\"foo\"]} }" ] - knownCorrect.forEach [ - prop.createModel(it).assertNoErrors() - ] - - // vvvvvvvvvvv - prop.createModel("{ dep: {/*empty*/} }") - .assertError(LfPackage::eINSTANCE.keyValuePairs, null, "Must specify one of 'version', 'path', or 'git'") - - // vvvvvvvvvvv - prop.createModel("{ dep: { unknown_key: \"\"} }") - .assertError(LfPackage::eINSTANCE.keyValuePair, null, "Unknown key: 'unknown_key'") - - // vvvv - prop.createModel("{ dep: { features: \"\" } }") - .assertError(LfPackage::eINSTANCE.element, null, "Expected an array of strings for key 'features'") - } - } From 7269930a0c3292b474c446849f5e3836eb7a4e70 Mon Sep 17 00:00:00 2001 From: Hou Seng Wong Date: Mon, 24 Jan 2022 11:57:40 -0800 Subject: [PATCH 10/11] fix some tests --- .../compiler/LinguaFrancaValidationTest.java | 32 +++++++++---------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaValidationTest.java b/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaValidationTest.java index d7d5d648f5..e493d5a67b 100644 --- a/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaValidationTest.java +++ b/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaValidationTest.java @@ -674,7 +674,7 @@ public void detectCausalityLoop() throws Exception { "target C;", "", "reactor X {", - " input x:int", + " input x:int;", " output y:int;", " reaction(x) -> y {=", " =}", @@ -682,7 +682,7 @@ public void detectCausalityLoop() throws Exception { "", "main reactor {", " a = new X()", - " a = new X()", + " b = new X()", " a.y -> b.x", " b.y -> a.x", "}" @@ -880,7 +880,7 @@ public void nonZeroTimeValueWithoutUnits() throws Exception { // main reactor { // timer t(42, 1 sec); // reaction(t) {= -// printf("Hello World.\n"); +// printf("Hello World.\\n"); // =} // } // """ @@ -890,7 +890,7 @@ public void nonZeroTimeValueWithoutUnits() throws Exception { "main reactor {", " timer t(42, 1 sec);", " reaction(t) {=", - " printf(\"Hello World.\n\");", + " printf(\"Hello World.\\n\");", " =}", "}"); validator.assertError(parseWithoutError(testCase), LfPackage.eINSTANCE.getValue(), null, "Missing time unit."); @@ -907,7 +907,7 @@ public void parameterTypeMismatch() throws Exception { // main reactor (p:int(0)) { // timer t(p, 1 sec); // reaction(t) {= -// printf("Hello World.\n"); +// printf("Hello World.\\n"); // =} // } // """ @@ -917,7 +917,7 @@ public void parameterTypeMismatch() throws Exception { "main reactor (p:int(0)) {", " timer t(p, 1 sec);", " reaction(t) {=", - " printf(\"Hello World.\n\");", + " printf(\"Hello World.\\n\");", " =}", "}"); validator.assertError(parseWithoutError(testCase), LfPackage.eINSTANCE.getValue(), @@ -935,7 +935,7 @@ public void targetCodeInTimeArgument() throws Exception { // main reactor { // timer t({=foo()=}, 1 sec); // reaction(t) {= -// printf("Hello World.\n"); +// printf("Hello World.\\n"); // =} // } // """ @@ -945,7 +945,7 @@ public void targetCodeInTimeArgument() throws Exception { "main reactor {", " timer t({=foo()=}, 1 sec);", " reaction(t) {=", - " printf(\"Hello World.\n\");", + " printf(\"Hello World.\\n\");", " =}", "}"); validator.assertError(parseWithoutError(testCase), LfPackage.eINSTANCE.getValue(), @@ -964,7 +964,7 @@ public void overflowingDeadlineC() throws Exception { // main reactor { // timer t; // reaction(t) {= -// printf("Hello World.\n"); +// printf("Hello World.\\n"); // =} deadline (40 hours) {= // =} // } @@ -975,7 +975,7 @@ public void overflowingDeadlineC() throws Exception { "main reactor {", "timer t;", " reaction(t) {=", - " printf(\"Hello World.\n\");", + " printf(\"Hello World.\\n\");", " =} deadline (40 hours) {=", " =}", "}"); @@ -996,8 +996,7 @@ public void overflowingParameterC() throws Exception { // main reactor(d:time(40 hours)) { // timer t; // reaction(t) {= -// printf("Hello World. -// "); +// printf("Hello World.\\n"); // =} deadline (d) {= // =} // } @@ -1008,8 +1007,7 @@ public void overflowingParameterC() throws Exception { "main reactor(d:time(40 hours)) {", "timer t;", " reaction(t) {=", - " printf(\"Hello World.", - "\");", + " printf(\"Hello World.\\n\");", " =} deadline (d) {=", " =}", "}"); @@ -1030,7 +1028,7 @@ public void overflowingAssignmentC() throws Exception { // reactor Print(d:time(39 hours)) { // timer t; // reaction(t) {= -// printf("Hello World.\n"); +// printf("Hello World.\\n"); // =} deadline (d) {= // =} // } @@ -1044,7 +1042,7 @@ public void overflowingAssignmentC() throws Exception { "reactor Print(d:time(39 hours)) {", " timer t;", " reaction(t) {=", - " printf(\"Hello World.\n\");", + " printf(\"Hello World.\\n\");", " =} deadline (d) {=", " =}", "}", @@ -1333,7 +1331,7 @@ public void recognizeIPV6() throws Exception { String.join(System.getProperty("line.separator"), "target C;", "reactor Y {}", - String.format("federated reactor X at [foo@%s]:4242 {", addr), + String.format("federated reactor at [foo@%s]:4242 {", addr), String.format(" y = new Y() at [%s]:2424; ", addr), "}") ); From dc25e216e90cb8c9cfa4a28d4fc02378f098fa67 Mon Sep 17 00:00:00 2001 From: Hou Seng Wong Date: Mon, 24 Jan 2022 12:04:40 -0800 Subject: [PATCH 11/11] fix more tests --- .../lflang/tests/compiler/LinguaFrancaValidationTest.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaValidationTest.java b/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaValidationTest.java index e493d5a67b..3e5cc075d2 100644 --- a/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaValidationTest.java +++ b/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaValidationTest.java @@ -1596,11 +1596,11 @@ private List synthesizeExamples(TargetPropertyType type, boolean correct return examples; } else { if (type instanceof UnionType) { - return synthesizeExamples(type, correct); + return synthesizeExamples((UnionType) type, correct); } else if (type instanceof ArrayType) { - return synthesizeExamples(type, correct); + return synthesizeExamples((ArrayType) type, correct); } else if (type instanceof DictionaryType) { - return synthesizeExamples(type, correct); + return synthesizeExamples((DictionaryType) type, correct); } else { Assertions.fail("Encountered an unknown type: " + type); }