/******************************************************************************* * Copyright (c) 2013 Rene Schneider, GEBIT Solutions GmbH 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 *******************************************************************************/ package de.gebit.integrity.runner; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeSet; import org.eclipse.emf.common.util.TreeIterator; import org.eclipse.emf.common.util.URI; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.resource.Resource.Diagnostic; import org.eclipse.xtext.resource.IResourceFactory; import org.eclipse.xtext.resource.XtextResource; import org.eclipse.xtext.resource.XtextResourceSet; import com.google.inject.Inject; import com.google.inject.Injector; import de.gebit.integrity.dsl.CallDefinition; import de.gebit.integrity.dsl.ConstantDefinition; import de.gebit.integrity.dsl.ConstantEntity; import de.gebit.integrity.dsl.ForkDefinition; import de.gebit.integrity.dsl.Model; import de.gebit.integrity.dsl.PackageDefinition; import de.gebit.integrity.dsl.SuiteDefinition; import de.gebit.integrity.dsl.TestDefinition; import de.gebit.integrity.dsl.VariableDefinition; import de.gebit.integrity.dsl.VariableEntity; import de.gebit.integrity.dsl.VariableOrConstantEntity; import de.gebit.integrity.dsl.VariantDefinition; import de.gebit.integrity.modelsource.ModelSourceExplorer; import de.gebit.integrity.runner.callbacks.TestRunnerCallback; import de.gebit.integrity.runner.exceptions.ModelAmbiguousException; import de.gebit.integrity.runner.exceptions.ModelLoadException; import de.gebit.integrity.runner.exceptions.ModelParseException; import de.gebit.integrity.runner.providers.TestResource; import de.gebit.integrity.runner.providers.TestResourceProvider; import de.gebit.integrity.utils.IntegrityDSLUtil; /** * The test model. There's not much more to say ;-) * * * @author Rene Schneider - initial API and implementation * */ public class TestModel { /** * All models. Every file is a model of itself (though they are all linked, of course). */ protected List<Model> models; /** * Suite names -> Suites. */ protected Map<String, SuiteDefinition> suiteMap = new HashMap<String, SuiteDefinition>(); /** * Variant names -> Variants. */ protected Map<String, VariantDefinition> variantMap = new HashMap<String, VariantDefinition>(); /** * Fork names -> Forks. */ protected Map<String, ForkDefinition> forkMap = new HashMap<String, ForkDefinition>(); /** * Call names -> Calls. */ protected Map<String, CallDefinition> callMap = new HashMap<String, CallDefinition>(); /** * Test names -> Tests. */ protected Map<String, TestDefinition> testMap = new HashMap<String, TestDefinition>(); /** * Variable/constant names -> Entities. */ protected Map<String, VariableOrConstantEntity> variableAndConstantMap = new HashMap<String, VariableOrConstantEntity>(); /** * Ambiguous definitions which are found during variable/suite/etc. indexing are collected here. */ protected Set<AmbiguousDefinition> ambiguousDefinitions = new HashSet<AmbiguousDefinition>(); /** * The Google Guice Injector. */ @Inject protected Injector injector; /** * The model source explorer. */ @Inject protected ModelSourceExplorer modelSourceExplorer; /** * Creates a new model from a bunch of single models (files). * * @param someModels * the models */ protected TestModel(List<Model> someModels) { models = someModels; Map<String, AmbiguousDefinition> tempDuplicateMap = new HashMap<String, AmbiguousDefinition>(); // Scan all models for suite definitions and variants and put them into the maps for fast access. // Also search for duplicate definitions! Test Models with duplicate definitions are technically fine, but // they may very likely result in strange behavior during execution, as the definition that is actually used // when for example a variable with multiple definitions is accessed is not predictable. for (Model tempModel : models) { TreeIterator<EObject> tempIter = tempModel.eAllContents(); while (tempIter.hasNext()) { EObject tempObject = tempIter.next(); String tempFullyQualifiedName = null; String tempType; if (tempObject instanceof SuiteDefinition) { SuiteDefinition tempSuite = (SuiteDefinition) tempObject; tempFullyQualifiedName = IntegrityDSLUtil.getQualifiedSuiteName(tempSuite); suiteMap.put(tempFullyQualifiedName, tempSuite); tempType = "suite"; } else if (tempObject instanceof VariantDefinition) { VariantDefinition tempVariant = (VariantDefinition) tempObject; tempFullyQualifiedName = IntegrityDSLUtil.getQualifiedVariantName(tempVariant); variantMap.put(tempFullyQualifiedName, tempVariant); tempType = "variant"; } else if (tempObject instanceof ForkDefinition) { ForkDefinition tempFork = (ForkDefinition) tempObject; tempFullyQualifiedName = IntegrityDSLUtil.getQualifiedForkName(tempFork); forkMap.put(tempFullyQualifiedName, tempFork); tempType = "fork"; } else if (tempObject instanceof CallDefinition) { CallDefinition tempCall = (CallDefinition) tempObject; tempFullyQualifiedName = IntegrityDSLUtil.getQualifiedCallName(tempCall); callMap.put(tempFullyQualifiedName, tempCall); tempType = "call"; } else if (tempObject instanceof TestDefinition) { TestDefinition tempTest = (TestDefinition) tempObject; tempFullyQualifiedName = IntegrityDSLUtil.getQualifiedTestName(tempTest); testMap.put(tempFullyQualifiedName, tempTest); tempType = "test"; } else if (tempObject instanceof VariableDefinition) { VariableEntity tempEntity = ((VariableDefinition) tempObject).getName(); tempFullyQualifiedName = IntegrityDSLUtil.getQualifiedVariableEntityName(tempEntity, true); variableAndConstantMap.put(tempFullyQualifiedName, tempEntity); tempType = "variable/constant"; } else if (tempObject instanceof ConstantDefinition) { ConstantEntity tempEntity = ((ConstantDefinition) tempObject).getName(); tempFullyQualifiedName = IntegrityDSLUtil.getQualifiedVariableEntityName(tempEntity, true); variableAndConstantMap.put(tempFullyQualifiedName, tempEntity); tempType = "variable/constant"; } else { continue; } AmbiguousDefinition tempDuplicateDefinition = new AmbiguousDefinition(tempFullyQualifiedName, tempType, tempObject); AmbiguousDefinition tempExistingDuplicate = tempDuplicateMap.get(tempDuplicateDefinition.getKey()); if (tempExistingDuplicate != null) { tempExistingDuplicate.addDefinition(tempObject); ambiguousDefinitions.add(tempExistingDuplicate); } else { tempDuplicateMap.put(tempDuplicateDefinition.getKey(), tempDuplicateDefinition); } } } } public List<Model> getModels() { return models; } public Map<String, SuiteDefinition> getSuiteMap() { return suiteMap; } public Set<AmbiguousDefinition> getDuplicateDefinitions() { return ambiguousDefinitions; } /** * Resolves a fully qualified suite name to the actual suite definition. * * @param aFullyQualifiedSuiteName * the suite name * @return the suite, or null if none was found */ public SuiteDefinition getSuiteByName(String aFullyQualifiedSuiteName) { return suiteMap.get(aFullyQualifiedSuiteName); } /** * Resolves a fully qualified variant name to the actual variant definition. * * @param aFullyQualifiedVariantName * the variant name * @return the variant, or null if none was found */ public VariantDefinition getVariantByName(String aFullyQualifiedVariantName) { return variantMap.get(aFullyQualifiedVariantName); } /** * Resolves a fully qualified fork name to the actual fork definition. * * @param aFullyQualifiedForkName * the fork name * @return the fork, or null if none was found */ public VariantDefinition getForkByName(String aFullyQualifiedForkName) { return variantMap.get(aFullyQualifiedForkName); } /** * Resolves a fully qualified variable/constant name to the actual entity. * * @param aFullyQualifiedName * the name to resolve * @return the entity, or null if none was found */ public VariableOrConstantEntity getVariableOrConstantByName(String aFullyQualifiedName) { return variableAndConstantMap.get(aFullyQualifiedName); } /** * Iterates through the whole model and searches for variable definitions hosted in packages (global variables). * * @return a set of variable definitions (sorted by fully qualified name) */ public TreeSet<VariableDefinition> getVariableDefinitionsInPackages() { TreeSet<VariableDefinition> tempResultSet = new TreeSet<VariableDefinition>( new Comparator<VariableDefinition>() { @Override public int compare(VariableDefinition aFirst, VariableDefinition aSecond) { String tempFirstName = IntegrityDSLUtil.getQualifiedVariableEntityName(aFirst.getName(), false); String tempSecondName = IntegrityDSLUtil.getQualifiedVariableEntityName(aSecond.getName(), false); return tempFirstName.compareTo(tempSecondName); } }); for (Model tempModel : models) { TreeIterator<EObject> tempIter = tempModel.eAllContents(); while (tempIter.hasNext()) { EObject tempObject = tempIter.next(); if (tempObject instanceof VariableDefinition) { if (tempObject.eContainer() instanceof PackageDefinition) { tempResultSet.add((VariableDefinition) tempObject); } } } } return tempResultSet; } /** * Iterates through the whole model and searches for constant definitions. * * @return a set of constant definitions (sorted by fully qualified name) */ public TreeSet<ConstantDefinition> getConstantDefinitionsInPackages() { TreeSet<ConstantDefinition> tempResultSet = new TreeSet<ConstantDefinition>( new Comparator<ConstantDefinition>() { @Override public int compare(ConstantDefinition aFirst, ConstantDefinition aSecond) { String tempFirstName = IntegrityDSLUtil.getQualifiedVariableEntityName(aFirst.getName(), false); String tempSecondName = IntegrityDSLUtil.getQualifiedVariableEntityName(aSecond.getName(), false); return tempFirstName.compareTo(tempSecondName); } }); for (Model tempModel : models) { TreeIterator<EObject> tempIter = tempModel.eAllContents(); while (tempIter.hasNext()) { EObject tempObject = tempIter.next(); if (tempObject instanceof ConstantDefinition) { if (tempObject.eContainer() instanceof PackageDefinition) { tempResultSet.add((ConstantDefinition) tempObject); } } } } return tempResultSet; } /** * Loads a {@link TestModel} from a given {@link TestResourceProvider}. During this process, the files provided by * the resource provider are parsed, the resulting models are linked and stored in the {@link TestModel} container.<br> * <br> * Errors, like unresolved symbols, will cause an exception. If a model is returned by this method, you can be sure * that everything was linked fine and the model can be executed by the {@link TestRunner}. * * @param aResourceProvider * the resource provider to use for loading the model * @param aSkipModelChecksFlag * if true, the test runner will skip the model consistency checks it would otherwise perform during the * dry run * @param aSetupClass * the setup class to use for EMF setup and Guice initialization (if null, the default class is used) * @return the test model ready for execution * @throws ModelLoadException * if any errors occur during loading (syntax errors or unresolvable references) */ public static TestModel loadTestModel(TestResourceProvider aResourceProvider, boolean aSkipModelChecksFlag, Class<? extends IntegrityDSLSetup> aSetupClass) throws ModelLoadException { Class<? extends IntegrityDSLSetup> tempSetupClass = aSetupClass; if (tempSetupClass == null) { tempSetupClass = IntegrityDSLSetup.class; } IntegrityDSLSetup tempSetup; try { tempSetup = tempSetupClass.newInstance(); } catch (InstantiationException exc) { throw new IllegalArgumentException("Provided setup class '" + tempSetupClass + "' could not be instantiated.", exc); } catch (IllegalAccessException exc) { throw new IllegalArgumentException("Provided setup class '" + tempSetupClass + "' could not be instantiated.", exc); } if (aResourceProvider.getClassLoader() != null) { tempSetup.setClassLoader(aResourceProvider.getClassLoader()); } tempSetup.setDisableModelChecks(aSkipModelChecksFlag); // Behold...the mighty Injector is born! Injector tempInjector = tempSetup.createInjectorAndDoEMFRegistration(); XtextResourceSet tempResourceSet = tempInjector.getInstance(XtextResourceSet.class); IResourceFactory tempResourceFactory = tempInjector.getInstance(IResourceFactory.class); ArrayList<Diagnostic> tempErrors = new ArrayList<Diagnostic>(); List<Model> tempModels = new LinkedList<Model>(); for (TestResource tempResourceName : aResourceProvider.getResourceNames()) { URI tempUri = tempResourceName.createPlatformResourceURI(); XtextResource tempResource = (XtextResource) tempResourceFactory.createResource(tempUri); tempResourceSet.getResources().add(tempResource); try { InputStream tempStream = aResourceProvider.openResource(tempResourceName); try { tempResource.load(tempStream, null); } finally { aResourceProvider.closeResource(tempResourceName, tempStream); } } catch (IOException exc) { throw new ModelLoadException("Encountered an I/O problem during model parsing.", exc); } System.out.println("Loaded Integrity Model File '" + tempResourceName + "': " + tempResource.getErrors().size() + " errors."); tempErrors.addAll(tempResource.getErrors()); Model tempModel = (Model) tempResource.getParseResult().getRootASTElement(); if (tempModel != null) { // may be null in case of an empty file tempModels.add(tempModel); } } if (!tempErrors.isEmpty()) { throw new ModelParseException("Encountered " + tempErrors.size() + " errors while parsing test model.", tempErrors); } TestModel tempModel = new TestModel(tempModels); tempInjector.injectMembers(tempModel); if (tempModel.getDuplicateDefinitions().size() > 0) { throw new ModelAmbiguousException("Encountered " + tempModel.getDuplicateDefinitions().size() + " ambiguous definitions in the test model.", tempModel.getDuplicateDefinitions()); } return tempModel; } /** * Initializes a fresh test runner instance, based on this test model. * * @param aCallback * the callback to use to report test results * @param someParameterizedConstants * Maps fully qualified constant names (must be those with the "parameterized" keyword) to their desired * value. This way, test execution can be parameterized from outside. * @param aRemotingPort * the port on which the remoting server should listen, or null if remoting should be disabled * @param aRemotingBindHost * the host name (or IP) to which the remoting server should bind * @param aRandomSeed * the seed for the {@link de.gebit.integrity.runner.operations.RandomNumberOperation;} (optional; * randomly determined if not given). * @param someCommandLineArguments * all command line arguments as given to the original Java programs' main routine (required for * forking!) * @return the initialized test runner instance * @throws IOException * if the remoting server startup fails */ public TestRunner initializeTestRunner(TestRunnerCallback aCallback, Map<String, String> someParameterizedConstants, Integer aRemotingPort, String aRemotingBindHost, Long aRandomSeed, String[] someCommandLineArguments) throws IOException { TestRunner tempRunner = injector.getInstance(TestRunner.class); tempRunner.initialize(this, someParameterizedConstants, aCallback, aRemotingPort, aRemotingBindHost, aRandomSeed, someCommandLineArguments); return tempRunner; } }