package org.overture.core.testing.samples; import java.io.File; import java.lang.reflect.Type; import java.util.List; import java.util.Vector; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; import org.overture.ast.definitions.SClassDefinition; import org.overture.ast.factory.AstFactoryTC; import org.overture.ast.lex.Dialect; import org.overture.ast.lex.LexLocation; import org.overture.ast.modules.AModuleModules; import org.overture.config.Settings; import org.overture.core.testing.ParamExternalsTest; import org.overture.core.testing.ParseTcFacade; import org.overture.parser.lex.LexException; import org.overture.parser.messages.VDMError; import org.overture.parser.syntax.ParserException; import org.overture.parser.util.ParserUtil; import org.overture.parser.util.ParserUtil.ParserResult; import org.overture.typechecker.ClassTypeChecker; import org.overture.typechecker.assistant.ITypeCheckerAssistantFactory; import org.overture.typechecker.assistant.TypeCheckerAssistantFactory; import org.overture.typechecker.util.TypeCheckerUtil; import org.overture.typechecker.util.TypeCheckerUtil.TypeCheckResult; import com.google.gson.reflect.TypeToken; /** * A simple test to demo the use of external test inputs. This class reuses * {@link SampleTestResult} but rather than printing the entire test model, it just * prints a success/failure message regarding the type checking of the test * source. </p> * Also note that since this test works with external inputs, the data provider * is already set up in {@link ParamExternalsTest}. To launch these testing simply * use the property <code>-DexternalTestsPath=/path/to/files/</code>. * </p> * This test also demonstrates explicitl invocation of the parser and type checker, * per {@link org.overture.core.testing.ParamFineGrainTest}. * </p> * <b>Note:</b> Due to some quirks with Parameterized JUnit testing, if the * property is not set, the test will still launch, only with 0 cases. It's fine * in Maven but in Eclipse you will get a single test run that does nothing. * * @author ldc */ @RunWith(Parameterized.class) public class SampleExternalsTest extends ParamExternalsTest<SampleTestResult> { // the update property for this test private static final String UPDATE_PROPERTY = "testing.update.testing.standard.SampleExternalsTest"; /** * For this test, the constructor only needs to pass the * parameters up to super. * * @param nameParameter * @param testParameter * @param resultParameter */ public SampleExternalsTest(String nameParameter, String testParameter, String resultParameter) { super(nameParameter, testParameter, resultParameter); } /** * Override result comparison with the one from {@link SampleTestResult}. Notice * that testInfo() is passed to the comparison method in order to print meaningful * error messages. */ @Override public void compareResults(SampleTestResult actual, SampleTestResult expected) { SampleTestResult.compare(actual, expected, testInfo()); } /** * Process the VDM source. External inputs can be negative so we control the * parsing and type checking ourselves. This makes the method much longer * and more complex. If we were certain the sources were correct, we could * just call a method from {@link ParseTcFacade}. * </p> * Remember that you must always track the VDM dialect of the source and the * only way to do this with external inputs is by placing them in dialect * folders. * </p>> * Because of this extra size and complexity it's a good idea to split up * the method according to dialect and do some dispatching. * * @return a {@link SampleTestResult} with a list of error messages or an all * clear message */ @Override public SampleTestResult processSource() { // Use file names only in error messages LexLocation.absoluteToStringLocation = false; SampleTestResult r = new SampleTestResult(); if (modelPath.contains("sltest")) { return processSl(); } if (modelPath.contains("rttest")) { try { return processRt(); } catch (ParserException e) { r.add("Error performing RT parsing in " + testName); } catch (LexException e) { r.add("Error performing RT lexing in " + testName); } } if (modelPath.contains("pptest")) { return processPp(); } r = new SampleTestResult(); r.add("Could not test " + testName + " unable to locate model in path: " + modelPath); return r; } /** * Return the update property for this test. In general, it's good practice * to do put it in a constant and return that. */ @Override protected String getUpdatePropertyString() { return UPDATE_PROPERTY; } /** * Return the {@link Type} or resust for this test. This is needed to help * out with reflection in the deserialization of results. */ @Override public Type getResultType() { Type resultType = new TypeToken<SampleTestResult>() { }.getType(); return resultType; } // All the code below is for handling parser and type checker calls. Now you know why you should avoid it. private SampleTestResult processPp() { Settings.dialect = Dialect.VDM_PP; SampleTestResult r = new SampleTestResult(); ParserResult<List<SClassDefinition>> pr = ParserUtil.parseOo(new File( modelPath)); if (pr.errors.isEmpty()) { TypeCheckResult<List<SClassDefinition>> tr = TypeCheckerUtil .typeCheck(pr, pr.result, new ClassTypeChecker(pr.result)); if (tr.errors.isEmpty()) { r.add(testName + " parses and type checks"); } else { for (VDMError e : tr.errors) { r.add(makeErrorMsg(e)); } } } else { for (VDMError e : pr.errors) { r.add(makeErrorMsg(e)); } } return r; } private SampleTestResult processRt() throws ParserException, LexException { Settings.dialect = Dialect.VDM_RT; SampleTestResult r = new SampleTestResult(); ParserResult<List<SClassDefinition>> pr = ParserUtil.parseOo(new File( modelPath)); if (pr.errors.isEmpty()) { ITypeCheckerAssistantFactory af = new TypeCheckerAssistantFactory(); List<SClassDefinition> classes = new Vector<SClassDefinition>(); classes.addAll(pr.result); classes.add(AstFactoryTC.newACpuClassDefinition(af)); classes.add(AstFactoryTC.newABusClassDefinition(af)); TypeCheckResult<List<SClassDefinition>> tr = TypeCheckerUtil .typeCheck(pr, pr.result, new ClassTypeChecker(pr.result)); if (tr.errors.isEmpty()) { r.add(testName + "parses and type checks"); } else { for (VDMError e : tr.errors) { r.add(makeErrorMsg(e)); } } } else { for (VDMError e : pr.errors) { r.add(makeErrorMsg(e)); } } return r; } private SampleTestResult processSl() { Settings.dialect = Dialect.VDM_SL; SampleTestResult r = new SampleTestResult(); File modelFile = new File(modelPath); ParserResult<List<AModuleModules>> pr = ParserUtil.parseSl(modelFile); if (pr.errors.isEmpty()) { TypeCheckResult<List<AModuleModules>> tr = TypeCheckerUtil .typeCheckSl(modelFile); if (tr.errors.isEmpty()) { r.add(testName + "parses and type checks"); } else { for (VDMError e : tr.errors) { r.add(makeErrorMsg(e)); } } } else { for (VDMError e : pr.errors) { r.add(makeErrorMsg(e)); } } return r; } private String makeErrorMsg(VDMError e) { StringBuilder sb = new StringBuilder(); sb.append("Error "); sb.append(e.number); sb.append(": "); sb.append(e.location.getFile().getName()); sb.append(" at "); sb.append(e.location.getStartLine()); sb.append(":"); sb.append(e.location.getStartPos()); sb.append(" "); sb.append(e.message.toString()); return sb.toString(); } }