blob: dbcf7d0fabcd755f1c58d60c013b65b438ee895e [file] [log] [blame]
/*********************************************************************************
* Copyright (c) 2017-2020 Robert Bosch GmbH and others.
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Robert Bosch GmbH - initial API and implementation
********************************************************************************
*/
package org.eclipse.app4mc.amalthea.converters.common.tests;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Stream;
import org.eclipse.app4mc.amalthea.converters.common.MigrationHelper;
import org.eclipse.app4mc.amalthea.converters.common.ServiceConstants;
import org.eclipse.app4mc.amalthea.converters.common.base.ICache;
import org.eclipse.app4mc.amalthea.converters.common.base.IConverter;
import org.eclipse.app4mc.amalthea.converters.common.base.IPostProcessor;
import org.eclipse.app4mc.amalthea.converters.common.utils.AmaltheaNamespaceRegistry;
import org.eclipse.app4mc.amalthea.converters.common.utils.HelperUtil;
import org.eclipse.app4mc.amalthea.converters.common.utils.ModelVersion;
import org.eclipse.app4mc.util.sessionlog.SessionLogger;
import org.jdom2.Attribute;
import org.jdom2.Document;
import org.jdom2.Element;
import org.jdom2.Namespace;
import org.junit.Rule;
import org.junit.rules.TestName;
public abstract class AbstractConverterTest {
public static final String GLOBAL_TEST_INPUT_DIRECTORY = "./TestModels/input";
public static final String GLOBAL_TEST_OUTPUT_DIRECTORY = "./TestModels/output";
protected final HashMap<File, Document> fileDocumentMapping = new HashMap<>();
protected final HashMap<String, String> filenameInputOutputMapping = new HashMap<>();
protected final boolean canExecuteTestCase;
protected String localOutputDirectory;
public AbstractConverterTest(final boolean canExecuteTestCase, final String... xmlFilesRelative) {
for (final String xmlFileRelativeLocation : xmlFilesRelative) {
final String inputXmlFilePath = GLOBAL_TEST_INPUT_DIRECTORY + File.separator + xmlFileRelativeLocation;
final String outputXmlFilePath = GLOBAL_TEST_OUTPUT_DIRECTORY + File.separator + xmlFileRelativeLocation;
this.filenameInputOutputMapping.put(inputXmlFilePath, outputXmlFilePath);
}
this.localOutputDirectory = new File(GLOBAL_TEST_OUTPUT_DIRECTORY, xmlFilesRelative[0]).getParent();
this.canExecuteTestCase = canExecuteTestCase;
}
public abstract ModelVersion getInputModelVersion();
public abstract ModelVersion getOutputModelVersion();
public void parseInputXMLFiles() {
this.fileDocumentMapping.clear();
if (!this.canExecuteTestCase) {
return;
}
try {
Set<String> inputFiles = this.filenameInputOutputMapping.keySet();
for (String inputFilePath : inputFiles) {
File inputFile = new File(inputFilePath);
// verify that the model version of the input file matches the configured input model version
String modelVersion = MigrationHelper.getModelVersion(inputFile);
assertEquals(getInputModelVersion().getVersion(), modelVersion);
this.fileDocumentMapping.put(inputFile, HelperUtil.loadFile(inputFilePath));
}
}
catch (final Exception e) {
fail(e.getMessage());
}
}
public void parseGeneratedXMLFiles() {
this.fileDocumentMapping.clear();
if (!this.canExecuteTestCase) {
return;
}
try {
final Collection<String> outputFiles = this.filenameInputOutputMapping.values();
for (final String outputFilePath : outputFiles) {
/*
* Special handling for fetching the files is implemented as in certain cases,
* file extensions are updated during model migration
*/
File targetFile = new File(outputFilePath);
if (!targetFile.exists()) {
String fileExtension = targetFile.getName().substring(targetFile.getName().lastIndexOf('.') + 1);
if (!fileExtension.equalsIgnoreCase("amxmi")) {
targetFile = new File(outputFilePath + ".amxmi");
}
}
// verify that the model version of the output file matches the configured output model version
String modelVersion = MigrationHelper.getModelVersion(targetFile);
assertEquals(getOutputModelVersion().getVersion(), modelVersion);
this.fileDocumentMapping.put(targetFile, HelperUtil.loadFile(outputFilePath));
}
}
catch (final Exception e) {
fail(e.getMessage());
}
}
protected List<ICache> buildCaches() {
final List<ICache> caches = new ArrayList<>();
return caches;
}
// accessibility updates in test cases are ok here, at runtime the activate method is called by the SCR
@SuppressWarnings("squid:S3011")
@SafeVarargs
final protected void invokeDependentConverters_ForAll_Files(
Set<File> files,
Map<File, Document> filename2documentMap,
List<ICache> caches,
Class<?>... converterClasss)
throws InstantiationException, IllegalAccessException, Exception {
if (converterClasss != null) {
SessionLogger logger = new SessionLogger();
for (Class<?> class1 : converterClasss) {
if (IConverter.class.isAssignableFrom(class1)) {
IConverter newInstance = (IConverter) class1.getDeclaredConstructor().newInstance();
// provide service properties programmatically
HashMap<String, Object> properties = new HashMap<>();
properties.put(ServiceConstants.INPUT_MODEL_VERSION_PROPERTY, getInputModelVersion().getVersion());
properties.put(ServiceConstants.OUTPUT_MODEL_VERSION_PROPERTY, getOutputModelVersion().getVersion());
Method[] methods = newInstance.getClass().getDeclaredMethods();
Method activateMethod = Arrays.stream(methods).filter(m -> "activate".equals(m.getName())).findFirst().orElse(null);
if (activateMethod != null) {
activateMethod.setAccessible(true);
if (activateMethod.getParameterCount() == 1) {
activateMethod.invoke(newInstance, properties);
} else if (activateMethod.getParameterCount() == 2) {
activateMethod.invoke(newInstance, properties, null);
}
}
Field[] fields = newInstance.getClass().getDeclaredFields();
Field loggerField = Arrays.stream(fields).filter(m -> "logger".equals(m.getName())).findFirst().orElse(null);
if (loggerField != null) {
loggerField.setAccessible(true);
loggerField.set(newInstance, logger);
}
//set cache
setCache(newInstance, caches);
for (File file : files) {
newInstance.convert(file, filename2documentMap, caches);
}
}
}
}
}
protected void invoke_postProcessor_ForAll_Files(Set<File> keySet,
Map<File, Document> filename2documentMap) {
List<IPostProcessor> postProcessors = buildPostProcessors();
for (IPostProcessor iPostProcessor : postProcessors) {
iPostProcessor.process(filename2documentMap);
}
}
protected List<IPostProcessor> buildPostProcessors() {
final List<IPostProcessor> processors = new ArrayList<IPostProcessor>();
return processors;
}
/**
* This method is used to verify if the AMALTHEA namespaces used in this document are of AMALTHEA 0.9.2
*
* @param document
*/
protected void namespaceVerification(final Document document) {
final List<Namespace> namespacesInScope = document.getRootElement().getNamespacesInScope();
for (final Namespace namespace : namespacesInScope) {
final String prefix = namespace.getPrefix();
if (prefix.equals("xmlns") || prefix.equals("xmi") || prefix.equals("xsi") || prefix.equals("")
|| prefix.equals("xml")) {
continue;
}
final boolean isAmaltheaNameSpacePrefix = AmaltheaNamespaceRegistry.isPrefixForVersion(getOutputModelVersion(), prefix);
if (isAmaltheaNameSpacePrefix) {
final boolean nsAvailable = AmaltheaNamespaceRegistry.isNamespaceAvailable(getOutputModelVersion(), namespace);
assertTrue("Expected namespace fom AMALTHEA " + getOutputModelVersion().getVersion() + " : " + namespace.getURI() + " --> "
+ document.getBaseURI(), nsAvailable);
}
else {
assertTrue("Invalid namespace is present in AMALTHEA model file : " + namespace.getURI(), false);
}
}
}
/**
* This method is used to save all migrated files
*
* @throws Exception
*/
protected void saveMigratedFiles() throws IOException {
saveFiles(true, this.localOutputDirectory);
}
private void saveFiles(final boolean updateFileNames, final String outputDirectoryLocation) throws IOException {
HelperUtil.updateFileName(this.fileDocumentMapping);
final List<File> outputFiles = new ArrayList<>();
final Set<File> keySet = this.fileDocumentMapping.keySet();
for (final File inputFile : keySet) {
String convertedFileName = inputFile.getName();
if (updateFileNames) {
// add amxmi extension to file names which do not have it (and are migrated to versions 1.1.1 or higher)
final int indexOfDot = convertedFileName.lastIndexOf('.');
final String extension = convertedFileName.substring(indexOfDot + 1);
if (extension.startsWith("amxmi") && !extension.equals("amxmi")) {
convertedFileName = convertedFileName + ".amxmi";
}
}
File outputFile = null;
if (outputDirectoryLocation != null && !outputDirectoryLocation.equals("")) {
final String location = outputDirectoryLocation + File.separator + convertedFileName;
HelperUtil.saveFile(this.fileDocumentMapping.get(inputFile), location, true, true);
outputFile = new File(location);
/* adding new file path */
outputFiles.add(outputFile);
}
else {
final String location = inputFile.getParentFile().getAbsolutePath() + File.separator
+ convertedFileName;
HelperUtil.saveFile(this.fileDocumentMapping.get(inputFile), location, true, true);
/* adding new file path */
outputFile = new File(location);
outputFiles.add(outputFile);
}
}
}
@Rule
public TestName testName = new TestName();
protected Namespace[] getNameSpaces() {
return AmaltheaNamespaceRegistry.getAllNamespacesBefore(getOutputModelVersion(), true, true);
}
/**
* This method is used to apply the Xpath and query the contents
*
* Note: This method is used only to query the Nodes, not the attributes
*
* @param element
* @param xpath
* @return
*/
protected List<Element> getXpathResult(final Element element, final String xpath) {
final StringBuilder xpathBuffer = new StringBuilder();
xpathBuffer.append(xpath);
final List<Element> elements = HelperUtil.getXpathResult(element, xpathBuffer.toString(), Element.class, getNameSpaces());
return elements;
}
/**
* This method is used to apply the Xpath and query the contents
*
* Note: This method is used only to query the attributes, not the elements
*
* @param element
* @param xpath
* @return
*/
protected List<Attribute> getXpathResult_Attributes(final Element element, final String xpath) {
final StringBuilder xpathBuffer = new StringBuilder();
xpathBuffer.append(xpath);
final List<Attribute> attributes = HelperUtil.getXpathResult(element, xpathBuffer.toString(), Attribute.class, getNameSpaces());
return attributes;
}
public void testConversion(final Class<?>... classes) {
if (!this.canExecuteTestCase) {
return;
}
try {
parseInputXMLFiles();
final List<ICache> caches = buildCaches();
invokeDependentConverters_ForAll_Files(this.fileDocumentMapping.keySet(), this.fileDocumentMapping,
caches, classes);
invoke_postProcessor_ForAll_Files(this.fileDocumentMapping.keySet(),this.fileDocumentMapping);
saveMigratedFiles();
}
catch (final Exception e) {
fail(e.getMessage());
}
}
public void verification() {
if (!this.canExecuteTestCase) {
return;
}
parseGeneratedXMLFiles();
final Collection<Document> values = this.fileDocumentMapping.values();
for (final Document document : values) {
/*- verifying the namespace */
namespaceVerification(document);
modelFileVerificationHook(document);
}
}
protected void modelFileVerificationHook(final Document document) {
}
public static void cleanOutputDirectory(String testOutputDir) {
// check if the local output directory already exists and clear it if it is not empty
Path localOutput = Paths.get(GLOBAL_TEST_OUTPUT_DIRECTORY + File.separator + testOutputDir);
if (Files.exists(localOutput)) {
try (Stream<Path> directoryStream = Files.list(localOutput)) {
if (directoryStream.findAny().isPresent()) {
try (Stream<Path> files = Files.walk(localOutput)) {
files
.sorted(Comparator.reverseOrder())
.forEach(path -> {
try {
Files.delete(path);
} catch (IOException e) {
fail("Clearing local output directory failed: " + e.getLocalizedMessage());
}
});
}
}
} catch (IOException e) {
fail("Clearing local output directory failed: " + e.getLocalizedMessage());
}
}
}
protected void setCache(IConverter converter, List<ICache> caches)
throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, SecurityException {
//subclasses will override
}
}