blob: 5f8839d3b8d5e538519b2669d173baf43237f0e2 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2017 Christian Pontesegger and others.
* 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:
* Christian Pontesegger - initial API and implementation
*******************************************************************************/
package org.eclipse.ease.lang.unittest;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import org.eclipse.debug.core.ILaunch;
import org.eclipse.ease.AbstractScriptEngine;
import org.eclipse.ease.IDebugEngine;
import org.eclipse.ease.IScriptEngine;
import org.eclipse.ease.Script;
import org.eclipse.ease.ScriptEngineException;
import org.eclipse.ease.debugging.ScriptStackTrace;
import org.eclipse.ease.debugging.model.EaseDebugVariable;
import org.eclipse.ease.lang.unittest.definition.Flag;
import org.eclipse.ease.lang.unittest.definition.ITestSuiteDefinition;
import org.eclipse.ease.lang.unittest.definition.IVariable;
import org.eclipse.ease.lang.unittest.execution.DefaultTestExecutionStrategy;
import org.eclipse.ease.lang.unittest.execution.ITestExecutionStrategy;
import org.eclipse.ease.lang.unittest.runtime.IRuntimeFactory;
import org.eclipse.ease.lang.unittest.runtime.ITestContainer;
import org.eclipse.ease.lang.unittest.runtime.ITestEntity;
import org.eclipse.ease.lang.unittest.runtime.ITestSuite;
import org.eclipse.ease.service.EngineDescription;
import org.eclipse.ease.service.IScriptService;
import org.eclipse.ease.service.ScriptService;
import org.eclipse.ease.service.ScriptType;
/**
* Script engine executing *.suite files and {@link ITestEntity} elements.
*/
public class TestSuiteScriptEngine extends AbstractScriptEngine implements IDebugEngine {
public static final String ENGINE_ID = "org.eclipse.ease.lang.unittest.engine";
/** Name of variable to be injected into executing test engines. */
public static final String TEST_FILE_VARIABLE = "__EASE_UnitTest_File";
public static final String TEST_SUITE_VARIABLE = "__EASE_UnitTest_Suite";
/**
* Debug method to print test execution results. This method is not needed for runtime functionality.
*
* @param entity
* test element to print status from (recursive)
* @param string
* indentation (typically use an empty string)
*/
private static void printStatus(ITestEntity entity, String indent) {
System.out.println(
indent + entity.getClass().getSimpleName() + " <" + entity.getName() + "> : " + entity.getStatus() + (" (" + entity.getDuration() + " ms)"));
if (entity instanceof ITestContainer) {
for (final ITestEntity child : ((ITestContainer) entity).getChildren())
printStatus(child, indent + "\t");
}
}
private final Map<String, Object> fVariables = new HashMap<>();
private final List<URL> fRegisteredJars = new ArrayList<>();
private final ITestContainer fTestRoot;
/** Debug variables. */
private ILaunch fLaunch = null;
private boolean fSuspendOnStartup;
private boolean fSuspendOnScriptLoad;
private boolean fShowDynamicCode;
/** Marker to indicate that this engine has received a termination request. */
private boolean fTerminated = false;
public TestSuiteScriptEngine() {
super("Script Testsuite");
fTestRoot = IRuntimeFactory.eINSTANCE.createTestFolder();
fTestRoot.setName("Root");
}
@Override
public void terminateCurrent() {
terminate();
}
@Override
public void registerJar(URL url) {
fRegisteredJars.add(url);
}
@Override
protected Object internalGetVariable(String name) {
return fVariables.get(name);
}
@Override
protected Map<String, Object> internalGetVariables() {
return fVariables;
}
@Override
protected boolean internalHasVariable(String name) {
return internalGetVariables().containsKey(name);
}
@Override
protected void internalSetVariable(String name, Object content) {
fVariables.put(name, content);
}
@Override
public Object removeVariable(String name) {
return fVariables.remove(name);
}
@Override
protected void setupEngine() throws ScriptEngineException {
fTerminated = false;
}
@Override
protected void teardownEngine() throws ScriptEngineException {
// nothing to do
}
private ITestSuite loadTestSuiteDefinition(Script script) throws Exception {
// load definition
final ITestSuiteDefinition definition = UnitTestHelper.loadTestSuite(script.getCodeStream());
if (definition != null) {
definition.setResource(script.getFile());
final ITestSuite runtimeSuite = UnitTestHelper.createRuntimeSuite(definition);
runtimeSuite.setResource(definition.getResource());
fTestRoot.getChildren().clear();
fTestRoot.getChildren().add(runtimeSuite);
// load variables
for (final IVariable variable : definition.getVariables())
setVariable(variable.getName(), variable.getContent());
return runtimeSuite;
}
return null;
}
@Override
protected Object execute(Script script, Object reference, String fileName, boolean uiThread) throws Throwable {
Object command = script.getCommand();
if ((!(command instanceof ITestEntity)) && (!(command instanceof FilteredTestCommand))) {
// not a testsuite. Probably a *.suite file or a stream providing suite data
try {
final ITestSuite runtimeSuite = loadTestSuiteDefinition(script);
if (runtimeSuite != null)
command = runtimeSuite;
else
throw new Exception("Could not load suite from resource: " + reference);
} catch (final Exception e) {
throw new Exception("Invalid testsuite content detected in: " + reference, e);
}
}
if (command instanceof ITestEntity) {
final ITestExecutionStrategy strategy = new DefaultTestExecutionStrategy();
strategy.prepareExecution(this, (ITestEntity) command);
strategy.execute((ITestEntity) command);
// not needed for productive environment, can be enabled for debugging purposes
// printStatus(((ITestEntity) command), "");
return command;
} else if (command instanceof FilteredTestCommand) {
// called when a test entity is re-executed with an applied filter
final ITestExecutionStrategy strategy = new DefaultTestExecutionStrategy();
strategy.prepareExecution(this, ((FilteredTestCommand) command).getTestRoot(), ((FilteredTestCommand) command).getActiveTests());
strategy.execute(((FilteredTestCommand) command).getTestRoot());
// not needed for productive environment, can be enabled for debugging purposes
// printStatus(((ITestEntity) command), "");
return command;
} else
throw new Exception("Cannot execute object \"" + command + "\"");
}
@Override
public void setupDebugger(ILaunch launch, boolean suspendOnStartup, boolean suspendOnScriptLoad, boolean showDynamicCode) {
fLaunch = launch;
fSuspendOnStartup = suspendOnStartup;
fSuspendOnScriptLoad = suspendOnScriptLoad;
fShowDynamicCode = showDynamicCode;
}
/**
* Get the root container for all executed test entities. This container gets created once and remains constant for the whole lifecycle of this engine. Its
* child elements may change however depending on what entities get executed.
*
* @return root container
*/
public ITestContainer getTestRoot() {
return fTestRoot;
}
@Override
public ScriptStackTrace getExceptionStackTrace() {
return null;
}
@Override
public void terminate() {
if (!fTerminated) {
fTerminated = true;
// propagate termination event to all engines
if (getTestRoot() != null)
getTestRoot().setTerminated(true);
super.terminate();
}
}
/**
* Create a script engine for a given testsuite and resource. The testsuite might provide information on the default engine to use. The resource might need
* a different engine to execute.
*
* @param testSuite
* testsuite to be executed from or <code>null</code>
* @param resource
* resource to execute or <code>null</code>
* @return script engine or <code>null</code>
*/
public IScriptEngine createScriptEngine(ITestSuite testSuite, Object resource) {
// load service directly to allow to work in headless mode
final IScriptService scriptService = ScriptService.getInstance();
EngineDescription candidate = null;
if ((testSuite != null) && (testSuite.getDefinition() != null)) {
if (testSuite.getDefinition().getFlag(Flag.PREFERRED_ENGINE_ID, "") != null)
candidate = scriptService.getEngineByID(testSuite.getDefinition().getFlag(Flag.PREFERRED_ENGINE_ID, ""));
}
// now lets see if candidate supports our script resource
if (resource != null) {
final String location = resource.toString();
if ((location != null) && (!location.isEmpty())) {
final ScriptType requiredScriptType = scriptService.getScriptType(location);
final List<EngineDescription> engines = requiredScriptType.getEngines();
// first pass: see if one of these engine matches with our preferred one
if (candidate != null) {
for (final EngineDescription description : engines) {
if (candidate.equals(description))
return prepareEngine(description.createEngine());
}
}
// we need to use a backup engine
// 2nd pass: look for a debug engine
for (final EngineDescription description : engines) {
if (description.supportsDebugging())
return prepareEngine(description.createEngine());
}
// final shot: take any engine available
if (!engines.isEmpty())
return prepareEngine(engines.get(0).createEngine());
}
}
if (candidate != null)
return prepareEngine(candidate.createEngine());
return null;
}
/**
* Prepare any engine created during test execution to use the same settings as this engine.
*
* @param engine
* engine to be prepared
* @return prepared engine instance
*/
private IScriptEngine prepareEngine(IScriptEngine engine) {
// link I/O streams
engine.setOutputStream(getOutputStream());
engine.setInputStream(getInputStream());
engine.setErrorStream(getErrorStream());
// enable debug support
if ((fLaunch != null) && (engine instanceof IDebugEngine)) {
// debug setup preferred
((IDebugEngine) engine).setupDebugger(fLaunch, fSuspendOnStartup, fSuspendOnScriptLoad, fShowDynamicCode);
}
// pass variables from suite
for (final Entry<String, Object> variable : getVariables().entrySet()) {
if (TEST_FILE_VARIABLE.equals(variable.getKey()))
continue;
if (TEST_SUITE_VARIABLE.equals(variable.getKey()))
continue;
engine.setVariable(variable.getKey(), variable.getValue());
}
// add registered jars rom suite
for (final URL jarLocation : fRegisteredJars)
engine.registerJar(jarLocation);
return engine;
}
@Override
public Collection<EaseDebugVariable> getVariables(Object scope) {
throw new UnsupportedOperationException();
}
}