blob: 1fae11363152f92d747ee4e4417ca5584a346f55 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2012, 2017 Willink Transformations 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:
* E.D.Willink - initial API and implementation
*******************************************************************************/
package org.eclipse.qvtd.xtext.qvtbase.tests;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.eclipse.emf.codegen.ecore.genmodel.GenModel;
import org.eclipse.emf.codegen.ecore.genmodel.GenModelPackage;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EPackage;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.ocl.examples.codegen.dynamic.OCL2JavaFileObject;
import org.eclipse.ocl.pivot.PivotTables;
import org.eclipse.ocl.pivot.internal.manager.MetamodelManagerInternal;
import org.eclipse.ocl.pivot.messages.StatusCodes;
import org.eclipse.ocl.pivot.resource.ASResource;
import org.eclipse.ocl.pivot.resource.CSResource;
import org.eclipse.ocl.pivot.resource.ProjectManager;
import org.eclipse.ocl.pivot.utilities.ClassUtil;
import org.eclipse.ocl.pivot.utilities.OCL;
import org.eclipse.ocl.pivot.utilities.PivotConstants;
import org.eclipse.ocl.pivot.utilities.PivotUtil;
import org.eclipse.qvtd.codegen.qvti.QVTiCodeGenOptions;
import org.eclipse.qvtd.codegen.qvti.java.QVTiCodeGenerator;
import org.eclipse.qvtd.compiler.AbstractCompilerChain;
import org.eclipse.qvtd.compiler.CompilerChain;
import org.eclipse.qvtd.pivot.qvtbase.Transformation;
import org.eclipse.qvtd.pivot.qvtbase.utilities.QVTbase;
import org.eclipse.qvtd.pivot.qvtimperative.ImperativeTransformation;
import org.eclipse.qvtd.pivot.qvtimperative.evaluation.BasicQVTiExecutor;
import org.eclipse.qvtd.pivot.qvtimperative.evaluation.QVTiEnvironmentFactory;
import org.eclipse.qvtd.pivot.qvtimperative.evaluation.QVTiIncrementalExecutor;
import org.eclipse.qvtd.pivot.qvtimperative.evaluation.QVTiTransformationExecutor;
import org.eclipse.qvtd.pivot.qvtimperative.utilities.QVTimperative;
import org.eclipse.qvtd.pivot.qvtimperative.utilities.QVTimperativeUtil;
import org.eclipse.qvtd.runtime.evaluation.Transformer;
import org.eclipse.qvtd.xtext.qvtbase.tests.utilities.TestsXMLUtil;
import org.eclipse.qvtd.xtext.qvtimperativecs.QVTimperativeCSPackage;
import org.eclipse.xtext.resource.XtextResource;
import junit.framework.TestCase;
public abstract class AbstractTestQVT extends QVTimperative
{
// FIXME move following clones to a Util class
public static @NonNull XtextResource as2cs(@NonNull OCL ocl, @NonNull ResourceSet resourceSet, @NonNull ASResource asResource, @NonNull URI outputURI, /*@NonNull*/ String csContentType) throws IOException {
XtextResource xtextResource = ClassUtil.nonNullState((XtextResource) resourceSet.createResource(outputURI, csContentType));
ocl.as2cs(asResource, (CSResource) xtextResource);
LoadTestCase.assertNoResourceErrors("Conversion failed", xtextResource);
//
// CS save
//
URI savedURI = ClassUtil.nonNullState(asResource.getURI());
asResource.setURI(outputURI.trimFileExtension().trimFileExtension().appendFileExtension(PivotConstants.OCL_AS_FILE_EXTENSION));
asResource.save(TestsXMLUtil.defaultSavingOptions);
asResource.setURI(savedURI);
try {
LoadTestCase.assertNoDiagnosticErrors("Concrete Syntax validation failed", xtextResource);
xtextResource.save(TestsXMLUtil.defaultSavingOptions);
}
catch (Throwable e) {
e.printStackTrace();
URI xmiURI = outputURI.appendFileExtension(".xmi");
Resource xmiResource = resourceSet.createResource(xmiURI);
xmiResource.getContents().addAll(ClassUtil.nullFree(xtextResource.getContents()));
xmiResource.save(TestsXMLUtil.defaultSavingOptions);
LoadTestCase.fail(e.toString());
}
return xtextResource;
}
public static @NonNull ASResource loadQVTiAS(@NonNull OCL ocl, @NonNull URI inputURI) {
Resource asResource = ocl.getMetamodelManager().getASResourceSet().getResource(inputURI, true);
assert asResource != null;
// List<String> conversionErrors = new ArrayList<String>();
// RootPackageCS documentCS = Ecore2OCLinEcore.importFromEcore(resourceSet, null, ecoreResource);
// Resource eResource = documentCS.eResource();
LoadTestCase.assertNoResourceErrors("Load failed", asResource);
// Resource xtextResource = resourceSet.createResource(outputURI, OCLinEcoreCSTPackage.eCONTENT_TYPE);
// XtextResource xtextResource = (XtextResource) resourceSet.createResource(outputURI);
// xtextResource.getContents().add(documentCS);
return (ASResource) asResource;
}
protected final @NonNull URI testsBaseURI;
protected final @NonNull String projectName;
// protected final @NonNull String testFolderName;
protected final @NonNull URI testFolderURI;
protected final @NonNull URI samplesBaseUri;
protected AbstractCompilerChain compilerChain = null;
private BasicQVTiExecutor interpretedExecutor = null;
private QVTiTransformationExecutor generatedExecutor = null;
private Set<@NonNull String> nsURIs = new HashSet<@NonNull String>();
public AbstractTestQVT(@NonNull URI testsBaseURI, @NonNull String projectName, @Nullable String testFolderName) {
this(testsBaseURI, projectName, testFolderName, "samples");
}
public AbstractTestQVT(@NonNull URI testsBaseURI, @NonNull String projectName, @Nullable String testFolderName, @Nullable String samplesFolderName) {
super(new QVTiEnvironmentFactory(LoadTestCase.getProjectMap(), null));
this.testsBaseURI = testsBaseURI.toString().endsWith("/") ? testsBaseURI : testsBaseURI.appendSegment("");
this.projectName = projectName;
// this.testFolderName = testFolderName;
URI testFolderURI = testFolderName != null ? testsBaseURI.appendSegment(testFolderName) : testsBaseURI;
this.testFolderURI = testFolderURI.toString().endsWith("/") ? testFolderURI : testFolderURI.appendSegment("");
URI samplesBaseUri = samplesFolderName != null ? testFolderURI.appendSegment(samplesFolderName) : testFolderURI;
this.samplesBaseUri = samplesBaseUri.toString().endsWith("/") ? samplesBaseUri : samplesBaseUri.appendSegment("");
}
protected void checkOutput(@NonNull Resource outputResource, @NonNull String expectedFile, @Nullable ModelNormalizer normalizer) throws IOException, InterruptedException {
URI referenceModelURI = URI.createURI(expectedFile).resolve(samplesBaseUri);
Resource referenceResource = outputResource.getResourceSet().getResource(referenceModelURI, true);
assert referenceResource != null;
if (normalizer != null) {
normalizer.normalize(referenceResource);
normalizer.normalize(outputResource);
}
LoadTestCase.assertSameModel(referenceResource, outputResource);
}
protected abstract @NonNull AbstractCompilerChain createCompilerChain(@NonNull URI prefixURI,
@NonNull Map<@NonNull String, @Nullable Map<CompilerChain.@NonNull Key<Object>, @Nullable Object>> options);
public @NonNull Class<? extends Transformer> createGeneratedClass(@NonNull Transformation asTransformation, @NonNull String @NonNull... genModelFiles) throws Exception {
ResourceSet resourceSet = getResourceSet();
resourceSet.getPackageRegistry().put(GenModelPackage.eNS_URI, GenModelPackage.eINSTANCE);
loadGenModels(genModelFiles);
QVTiCodeGenerator cg = new QVTiCodeGenerator(getEnvironmentFactory(), asTransformation);
QVTiCodeGenOptions options = cg.getOptions();
options.setUseNullAnnotations(true);
setPackagePrefixOption(options);
cg.generateClassFile();
cg.saveSourceFile("../" + projectName + "/test-gen/");
File explicitClassPath = new File("../" + projectName + "/bin");
String qualifiedClassName = cg.getQualifiedName();
String javaCodeSource = cg.generateClassFile();
OCL2JavaFileObject.saveClass(ClassUtil.nonNullState(explicitClassPath.toString()), qualifiedClassName, javaCodeSource);
@SuppressWarnings("unchecked")
Class<? extends Transformer> txClass = (Class<? extends Transformer>) OCL2JavaFileObject.loadExplicitClass(explicitClassPath, qualifiedClassName);
if (txClass == null) {
TestCase.fail("Failed to compile transformation");
throw new UnsupportedOperationException();
}
return txClass;
}
public void createGeneratedExecutor(@NonNull Transformation asTransformation, @NonNull String @NonNull... genModelFiles) throws Exception {
Class<? extends Transformer> txClass = createGeneratedClass(asTransformation, genModelFiles);
createGeneratedExecutor(txClass);
}
public QVTiTransformationExecutor createGeneratedExecutor(@NonNull Class<? extends Transformer> txClass) throws Exception {
return generatedExecutor = new QVTiTransformationExecutor(getEnvironmentFactory(), txClass);
}
public @NonNull BasicQVTiExecutor createInterpretedExecutor(@NonNull ImperativeTransformation asTransformation) throws Exception {
interpretedExecutor = new QVTiIncrementalExecutor(getEnvironmentFactory(), asTransformation, QVTiIncrementalExecutor.Mode.LAZY);
return interpretedExecutor;
}
public @Nullable Resource createModel(@NonNull String modelName, @NonNull String modelFile) {
URI modelURI = samplesBaseUri.appendSegment(modelFile);
return interpretedExecutor.createModel(modelName, modelURI, null);
}
@Override
public synchronized void dispose() {
super.dispose();
if (interpretedExecutor != null) {
interpretedExecutor.dispose();
}
if (compilerChain != null) {
compilerChain.dispose();
}
/**
* Remove the eInstances from the EPackage.Registry.INSTANCE so that global registrations from the calling test
* do not confuse subsequent tests that may want to use dynamic models.
*/
for (String nsURI : nsURIs) {
EPackage.Registry.INSTANCE.remove(nsURI);
}
}
protected @NonNull Class<? extends Transformer> doBuild(@NonNull String testFileName, @NonNull String outputName,
@NonNull Map<@NonNull String, @Nullable Map<CompilerChain.@NonNull Key<Object>, @Nullable Object>> options,
@NonNull String @NonNull... genModelFiles) throws Exception {
compilerChain = createCompilerChain(testFolderURI.appendSegment(testFileName), options);
ImperativeTransformation asTransformation = compilerChain.compile(outputName);
URI txURI = asTransformation.eResource().getURI();
if (txURI != null) {
URI inputURI = txURI;
URI serializedURI = txURI.trimFileExtension().appendFileExtension("serialized.qvti");
doSerialize(inputURI, serializedURI);
}
Class<? extends Transformer> txClass = compilerChain.generate(asTransformation, genModelFiles);
createGeneratedExecutor(txClass); // FIXME this is convenient for a single execution, but dangerous for multi-execution
return txClass;
}
protected @NonNull ImperativeTransformation doCompile(@NonNull String testFileName, @NonNull String outputName,
@NonNull Map<@NonNull String, @Nullable Map<CompilerChain.@NonNull Key<Object>, @Nullable Object>> options) throws Exception {
compilerChain = createCompilerChain(testFolderURI.appendSegment(testFileName), options);
ImperativeTransformation transformation = compilerChain.compile(outputName);
URI txURI = transformation.eResource().getURI();
if (txURI != null) {
URI inputURI = txURI;
URI serializedURI = txURI.trimFileExtension().appendFileExtension("serialized.qvti");
doSerialize(inputURI, serializedURI);
}
return transformation;
}
protected static XtextResource doSerialize(@NonNull URI inputURI, @NonNull URI serializedURI) throws IOException {
ResourceSet resourceSet = new ResourceSetImpl();
//
// Load QVTiAS
//
OCL ocl = QVTbase.newInstance(OCL.NO_PROJECTS);
ocl.getEnvironmentFactory().setSeverity(PivotTables.STR_Variable_c_c_CompatibleInitialiserType, StatusCodes.Severity.IGNORE);
try {
ASResource asResource = loadQVTiAS(ocl, inputURI);
LoadTestCase.assertNoResourceErrors("Normalisation failed", asResource);
LoadTestCase.assertNoUnresolvedProxies("Normalisation invalid", asResource);
LoadTestCase.assertNoValidationErrors("Normalisation invalid", asResource);
//
// Pivot to CS
//
XtextResource xtextResource = as2cs(ocl, resourceSet, asResource, serializedURI, QVTimperativeCSPackage.eCONTENT_TYPE);
resourceSet.getResources().clear();
QVTimperative qvti = QVTimperative.newInstance(ProjectManager.NO_PROJECTS, null);
try {
ImperativeTransformation asTransformation = QVTimperativeUtil.loadTransformation(qvti.getEnvironmentFactory(), serializedURI, false);
Resource asResource2 = asTransformation.eResource();
assert asResource2 != null;
LoadTestCase.assertNoResourceErrors("Load failed", asResource2);
LoadTestCase.assertNoUnresolvedProxies("Load invalid", asResource2);
LoadTestCase.assertNoValidationErrors("Load invalid", asResource2);
}
finally {
qvti.dispose();
qvti = null;
}
return xtextResource;
}
finally {
ocl.dispose();
ocl = null;
}
}
public Transformer executeTransformation() throws Exception {
if (interpretedExecutor != null) {
interpretedExecutor.execute();
interpretedExecutor.saveModels(TestsXMLUtil.defaultSavingOptions);
// interpretedExecutor.saveModels(TestsXMLUtil.defaultSavingOptions);
return null;
}
else {
Transformer transformer = generatedExecutor.getTransformer();
transformer.run(); // FIXME BUG 511028
// if (!transformer.run()) {
// throw new Exception("Failed to execute");
// }
return transformer;
}
}
@Override
public @NonNull QVTiEnvironmentFactory getEnvironmentFactory() {
return super.getEnvironmentFactory();
}
public @NonNull Collection<@NonNull ? extends Object> getRootObjects(@NonNull String modelName) {
if (interpretedExecutor != null) {
return interpretedExecutor.getRootObjects(modelName);
}
else {
return generatedExecutor.getTransformer().getRootObjects(modelName);
}
}
public @NonNull Map<Object, Object> getSaveOptions() {
Map<Object, Object> saveOptions = new HashMap<Object, Object>(TestsXMLUtil.defaultSavingOptions);
saveOptions.put(ASResource.OPTION_NORMALIZE_CONTENTS, Boolean.TRUE);
return saveOptions;
}
public void installClassName(@NonNull String className) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
Class<?> middleClass = Class.forName(className);
Field middleField = middleClass.getDeclaredField("eINSTANCE");
EPackage middleEPackage = (EPackage) middleField.get(null);
getResourceSet().getPackageRegistry().put(middleEPackage.getNsURI(), middleEPackage);
}
protected void loadGenModel(@NonNull URI genModelURI) {
ResourceSet resourceSet = getResourceSet();
MetamodelManagerInternal metamodelManager = getMetamodelManager();
Resource csGenResource = resourceSet.getResource(genModelURI, true);
for (EObject eObject : csGenResource.getContents()) {
if (eObject instanceof GenModel) {
GenModel genModel = (GenModel)eObject;
genModel.reconcile();
metamodelManager.addGenModel(genModel);
}
}
}
/**
* Explicitly install the eInstances that would normally make it into the ProjectMap from extension point registrations.
* Test models are not registered via extension point so we have to do this manually.
*/
public void installEPackages(EPackage... eInstances) {
ResourceSetImpl resourceSet = (ResourceSetImpl) getResourceSet();
for (EPackage eInstance : eInstances) {
String nsURI = eInstance.getNsURI();
if (nsURI != null) {
nsURIs.add(nsURI);
}
resourceSet.getURIResourceMap().put(testFolderURI.appendSegment(eInstance.getName()+".ecore"), eInstance.eResource());
}
}
public @Nullable Resource loadInput(@NonNull String modelName, @NonNull String modelFile) {
URI modelURI = URI.createURI(modelFile).resolve(samplesBaseUri);
if (interpretedExecutor != null) {
return interpretedExecutor.loadModel(modelName, modelURI);
}
else {
// Resource inputResource = getResourceSet().getResource(modelURI, true);
ResourceSet resourceSet = environmentFactory.getMetamodelManager().getASResourceSet(); // FIXME get package registrations in exteranl RespurcSet
PivotUtil.initializeLoadOptionsToSupportSelfReferences(resourceSet);
Resource inputResource = resourceSet.getResource(modelURI, true);
generatedExecutor.getTransformer().addRootObjects(modelName, ClassUtil.nonNullState(inputResource.getContents()));
return inputResource;
}
}
protected abstract void loadGenModels(@NonNull String @NonNull... genModelFiles);
public void removeRegisteredPackage(@NonNull String ePackageClassName, boolean exceptionThrown) throws Exception {
if (exceptionThrown) { // Don't compound an earlier failure
try {
Class<?> ePackageClass = Class.forName(ePackageClassName);
Field eNsURIField = ePackageClass.getField("eNS_URI");
String nsURI = String.valueOf(eNsURIField.get(null));
EPackage.Registry.INSTANCE.remove(nsURI);
} catch (Exception e) {
e.printStackTrace();
}
}
else {
Class<?> ePackageClass = Class.forName(ePackageClassName);
Field eNsURIField = ePackageClass.getField("eNS_URI");
String nsURI = String.valueOf(eNsURIField.get(null));
EPackage.Registry.INSTANCE.remove(nsURI);
}
}
public @NonNull Resource saveOutput(@NonNull String modelName, @NonNull String modelFile, @Nullable String expectedFile, @Nullable ModelNormalizer normalizer) throws IOException, InterruptedException {
URI modelURI = URI.createURI(modelFile).resolve(samplesBaseUri);
ResourceSet resourceSet = /*getResourceSet()*/environmentFactory.getMetamodelManager().getASResourceSet();
Resource outputResource;
if (interpretedExecutor != null) {
outputResource = interpretedExecutor.saveModel(modelName, modelURI, null, getSaveOptions());
}
else {
outputResource = resourceSet.createResource(modelURI);
outputResource.getContents().addAll(generatedExecutor.getTransformer().getRootEObjects(modelName));
outputResource.save(getSaveOptions());
}
assert outputResource != null;
if (expectedFile != null) {
checkOutput(outputResource, expectedFile, normalizer);
}
return outputResource;
}
protected void setPackagePrefixOption(@NonNull QVTiCodeGenOptions options) {}
}