/******************************************************************************* * Copyright (c) 2009-2012 CWI * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: Paul Klint, Jurgen Vinju */ package org.rascalmpl.test.infrastructure; import java.io.IOException; import java.io.PrintWriter; import java.net.URISyntaxException; import java.util.ArrayList; import java.util.Collections; import java.util.LinkedList; import java.util.List; import java.util.Queue; import org.junit.runner.Description; import org.junit.runner.Result; import org.junit.runner.Runner; import org.junit.runner.notification.Failure; import org.junit.runner.notification.RunNotifier; import org.rascalmpl.interpreter.Evaluator; import org.rascalmpl.interpreter.ITestResultListener; import org.rascalmpl.interpreter.NullRascalMonitor; import org.rascalmpl.interpreter.TestEvaluator; import org.rascalmpl.interpreter.asserts.ImplementationError; import org.rascalmpl.interpreter.env.GlobalEnvironment; import org.rascalmpl.interpreter.env.ModuleEnvironment; import org.rascalmpl.interpreter.load.StandardLibraryContributor; import org.rascalmpl.interpreter.result.AbstractFunction; import org.rascalmpl.uri.URIResolverRegistry; import org.rascalmpl.uri.URIUtil; import org.rascalmpl.value.ISourceLocation; import org.rascalmpl.values.ValueFactoryFactory; public class RascalJUnitTestRunner extends Runner { private static Evaluator evaluator; private static GlobalEnvironment heap; private static ModuleEnvironment root; private static PrintWriter stderr; private static PrintWriter stdout; private Description desc; private String prefix; static { heap = new GlobalEnvironment(); root = heap.addModule(new ModuleEnvironment("___junit_test___", heap)); stderr = new PrintWriter(System.err); stdout = new PrintWriter(System.out); evaluator = new Evaluator(ValueFactoryFactory.getValueFactory(), stderr, stdout, root, heap); evaluator.addRascalSearchPathContributor(StandardLibraryContributor.getInstance()); evaluator.getConfiguration().setErrors(true); } public RascalJUnitTestRunner(Class<?> clazz) { this(clazz.getAnnotation(RascalJUnitTestPrefix.class).value()); try { Object instance = clazz.newInstance(); if (instance instanceof IRascalJUnitTestSetup) { ((IRascalJUnitTestSetup) instance).setup(evaluator); } else { evaluator.addRascalSearchPath(URIUtil.rootLocation("tmp")); } } catch (InstantiationException e) { throw new ImplementationError("could not setup tests for: " + clazz.getCanonicalName(), e); } catch (IllegalAccessException e) { throw new ImplementationError("could not setup tests for: " + clazz.getCanonicalName(), e); } } public RascalJUnitTestRunner(String prefix) { // remove all the escapes (for example in 'lang::rascal::\syntax') this.prefix = prefix; } public static String computeTestName(String name, ISourceLocation loc) { return name + ": <" + loc.getOffset() +"," + loc.getLength() +">"; } public static List<String> getRecursiveModuleList(ISourceLocation root) throws IOException { List<String> result = new ArrayList<>(); Queue<ISourceLocation> todo = new LinkedList<>(); todo.add(root); while (!todo.isEmpty()) { ISourceLocation currentDir = todo.poll(); String prefix = currentDir.getPath().replaceFirst(root.getPath(), "").replaceFirst("/", "").replaceAll("/", "::"); for (ISourceLocation ent : URIResolverRegistry.getInstance().list(currentDir)) { if (ent.getPath().endsWith(".rsc")) { if (prefix.isEmpty()) { result.add(URIUtil.getLocationName(ent).replace(".rsc", "")); } else { result.add(prefix + "::" + URIUtil.getLocationName(ent).replace(".rsc", "")); } } else { if (URIResolverRegistry.getInstance().isDirectory(ent)) { todo.add(ent); } } } } return result; } @Override public Description getDescription() { Description desc = Description.createSuiteDescription(prefix); this.desc = desc; try { List<String> modules = getRecursiveModuleList(evaluator.getValueFactory().sourceLocation("std", "", "/" + prefix.replaceAll("::", "/"))); Collections.shuffle(modules); // make sure the import order is different, not just the reported modules for (String module : modules) { String name = prefix + "::" + module; try { evaluator.doImport(new NullRascalMonitor(), name); } catch (Throwable e) { throw new RuntimeException("Could not import " + name + " for testing...", e); } Description modDesc = Description.createSuiteDescription(name); desc.addChild(modDesc); // the order of the tests aren't decided by this list so no need to randomly order them. for (AbstractFunction f : heap.getModule(name.replaceAll("\\\\","")).getTests()) { modDesc.addChild(Description.createTestDescription(getClass(), computeTestName(f.getName(), f.getAst().getLocation()))); } } return desc; } catch (IOException e) { throw new RuntimeException("could not create test suite", e); } catch (URISyntaxException e) { throw new RuntimeException("could not create test suite", e); } } @Override public void run(final RunNotifier notifier) { if (desc == null) { desc = getDescription(); } notifier.fireTestRunStarted(desc); for (Description mod : desc.getChildren()) { TestEvaluator runner = new TestEvaluator(evaluator, new Listener(notifier, mod)); runner.test(mod.getDisplayName()); } notifier.fireTestRunFinished(new Result()); } private final class Listener implements ITestResultListener { private final RunNotifier notifier; private final Description module; private Listener(RunNotifier notifier, Description module) { this.notifier = notifier; this.module = module; } private Description getDescription(String name, ISourceLocation loc) { String testName = computeTestName(name, loc); for (Description child : module.getChildren()) { if (child.getMethodName().equals(testName)) { return child; } } throw new IllegalArgumentException(name + " test was never registered"); } @Override public void start(String context, int count) { notifier.fireTestRunStarted(module); } @Override public void ignored(String test, ISourceLocation loc) { notifier.fireTestIgnored(getDescription(test, loc)); } @Override public void report(boolean successful, String test, ISourceLocation loc, String message, Throwable t) { Description desc = getDescription(test, loc); notifier.fireTestStarted(desc); if (!successful) { notifier.fireTestFailure(new Failure(desc, t != null ? t : new Exception(message != null ? message : "no message"))); } else { notifier.fireTestFinished(desc); } } @Override public void done() { notifier.fireTestRunFinished(new Result()); } } }