| /******************************************************************************* |
| * Copyright (c) 2016, 2021 Obeo. |
| * 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: |
| * Obeo - initial API and implementation |
| * |
| *******************************************************************************/ |
| package org.eclipse.acceleo.tests.utils; |
| |
| import static org.junit.Assert.assertEquals; |
| import static org.junit.Assert.fail; |
| |
| import java.io.BufferedInputStream; |
| import java.io.File; |
| import java.io.FileFilter; |
| import java.io.FileInputStream; |
| import java.io.FileNotFoundException; |
| import java.io.FileOutputStream; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.InputStreamReader; |
| import java.io.OutputStream; |
| import java.io.UnsupportedEncodingException; |
| import java.net.URL; |
| import java.net.URLClassLoader; |
| import java.nio.file.Path; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collection; |
| import java.util.List; |
| |
| import org.eclipse.acceleo.Module; |
| import org.eclipse.acceleo.aql.AcceleoEnvironment; |
| import org.eclipse.acceleo.aql.IAcceleoEnvironment; |
| import org.eclipse.acceleo.aql.evaluation.AcceleoEvaluator; |
| import org.eclipse.acceleo.aql.evaluation.writer.DefaultGenerationStrategy; |
| import org.eclipse.acceleo.aql.parser.AcceleoAstResult; |
| import org.eclipse.acceleo.aql.parser.AcceleoParser; |
| import org.eclipse.acceleo.aql.parser.ModuleLoader; |
| import org.eclipse.acceleo.aql.validation.AcceleoValidator; |
| import org.eclipse.acceleo.query.runtime.IValidationMessage; |
| import org.eclipse.acceleo.query.runtime.impl.namespace.ClassLoaderQualifiedNameResolver; |
| import org.eclipse.acceleo.query.runtime.impl.namespace.JavaLoader; |
| import org.eclipse.acceleo.query.runtime.impl.namespace.QualifiedNameQueryEnvironment; |
| import org.eclipse.acceleo.query.runtime.namespace.IQualifiedNameQueryEnvironment; |
| import org.eclipse.acceleo.query.runtime.namespace.IQualifiedNameResolver; |
| import org.eclipse.emf.common.util.URI; |
| import org.eclipse.emf.ecore.resource.ResourceSet; |
| import org.eclipse.emf.ecore.resource.URIConverter; |
| import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl; |
| import org.eclipse.emf.ecore.xmi.impl.XMIResourceFactoryImpl; |
| import org.junit.AfterClass; |
| import org.junit.BeforeClass; |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| import org.junit.runners.Parameterized; |
| |
| /** |
| * Run a folder with templates as a test suite JUnit. |
| * |
| * @author <a href="mailto:yvan.lussaud@obeo.fr">Yvan Lussaud</a> |
| */ |
| @RunWith(Parameterized.class) |
| public abstract class AbstractLanguageTestSuite { |
| |
| /** |
| * UTF-8 content. |
| */ |
| public static final String UTF_8 = "UTF-8"; |
| |
| /** |
| * The default EOL character to use. |
| */ |
| public static final char DEFAULT_END_OF_LINE_CHARACTER = '\n'; |
| |
| /** |
| * The {@link MemoryURIHandler} that check we don't have adherence to {@link File}. |
| */ |
| private static MemoryURIHandler uriHandler = new MemoryURIHandler(); |
| |
| /** |
| * The {@link AcceleoAstResult}. |
| */ |
| protected final AcceleoAstResult astResult; |
| |
| /** |
| * The {@link IAcceleoEnvironment}. |
| */ |
| protected final IAcceleoEnvironment environment; |
| |
| /** |
| * The {@link AcceleoEvaluator}. |
| */ |
| protected final AcceleoEvaluator evaluator; |
| |
| /** |
| * The memory destination {@link String}. |
| */ |
| protected final String memoryDestinationString; |
| |
| /** |
| * The memoty destination {@link URI}. |
| */ |
| protected final URI memoryDestination; |
| |
| /** |
| * The {@link ModuleAstSerializer}. |
| */ |
| private final ModuleAstSerializer moduleAstSerializer = new ModuleAstSerializer(); |
| |
| /** |
| * The test folder path. |
| */ |
| private final String testFolderPath; |
| |
| /** |
| * The module qualified name. |
| */ |
| private final String qualifiedName; |
| |
| /** |
| * Constructor. |
| * |
| * @param testFolder |
| * the test folder path |
| * @throws IOException |
| * if the tested template can't be read |
| */ |
| public AbstractLanguageTestSuite(String testFolder) throws IOException { |
| this.memoryDestinationString = "acceleotests://" + testFolder + "/"; |
| this.memoryDestination = URI.createURI(memoryDestinationString); |
| this.testFolderPath = testFolder; |
| final File testFolderFile = new File(testFolderPath); |
| final File moduleFile = getModuleFile(testFolderFile); |
| |
| final Path rootPath = testFolderFile.toPath().getName(0); |
| final URL[] urls = new URL[] {testFolderFile.toPath().getName(0).toUri().toURL() }; |
| |
| final ClassLoader classLoader = new URLClassLoader(urls, getClass().getClassLoader()); |
| final IQualifiedNameResolver resolver = new ClassLoaderQualifiedNameResolver(classLoader, |
| AcceleoParser.QUALIFIER_SEPARATOR); |
| final IQualifiedNameQueryEnvironment queryEnvironment = new QualifiedNameQueryEnvironment(resolver); |
| this.environment = new AcceleoEnvironment(resolver, queryEnvironment, new DefaultGenerationStrategy(), |
| memoryDestination); |
| |
| evaluator = new AcceleoEvaluator(this.environment, queryEnvironment.getLookupEngine()); |
| resolver.addLoader(new ModuleLoader(new AcceleoParser(), evaluator)); |
| resolver.addLoader(new JavaLoader(AcceleoParser.QUALIFIER_SEPARATOR)); |
| |
| String namespace = rootPath.relativize(testFolderFile.toPath()).toString().replace(File.separator, |
| "::") + "::"; |
| qualifiedName = namespace + moduleFile.getName().substring(0, moduleFile.getName().lastIndexOf('.')); |
| final Object resolved = resolver.resolve(qualifiedName); |
| if (resolved instanceof Module) { |
| astResult = ((Module)resolved).getAst(); |
| } else { |
| astResult = null; |
| } |
| } |
| |
| /** |
| * Registers {@link MemoryURIHandler}. |
| */ |
| @BeforeClass |
| public static void beforeClass() { |
| URIConverter.INSTANCE.getURIHandlers().add(0, uriHandler); |
| } |
| |
| /** |
| * Removes {@link MemoryURIHandler}. |
| */ |
| @AfterClass |
| public static void afterClass() { |
| uriHandler.clear(); |
| URIConverter.INSTANCE.getURIHandlers().remove(uriHandler); |
| } |
| |
| /** |
| * Gets the test folder path. |
| * |
| * @return the test folder path |
| */ |
| protected String getTestFolderPath() { |
| return testFolderPath; |
| } |
| |
| /** |
| * Gets the {@link ResourceSet}. |
| * |
| * @return the {@link ResourceSet} |
| */ |
| protected ResourceSet getResourceSet() { |
| ResourceSetImpl res = new ResourceSetImpl(); |
| |
| res.getResourceFactoryRegistry().getExtensionToFactoryMap().put("*", new XMIResourceFactoryImpl()); |
| |
| return res; |
| } |
| |
| /** |
| * Tests the parsing by comparing a textual representation of the AST. |
| * |
| * @throws IOException |
| * if the expected AST file can't be read |
| * @throws FileNotFoundException |
| * if the expected AST file can't be found |
| */ |
| @Test |
| public void parsing() throws FileNotFoundException, IOException { |
| final File expectedASTFile = getExpectedASTFile(new File(testFolderPath)); |
| final String actualAst = moduleAstSerializer.serialize(astResult); |
| if (!expectedASTFile.exists()) { |
| final File actualASTFile = getActualASTFile(new File(testFolderPath)); |
| if (!actualASTFile.exists()) { |
| actualASTFile.createNewFile(); |
| } |
| setContent(new FileOutputStream(actualASTFile), UTF_8, actualAst); |
| fail("file doesn't exist."); |
| } |
| try (FileInputStream stream = new FileInputStream(expectedASTFile)) { |
| final String expectedAst = getContent(stream, UTF_8); |
| assertEquals(expectedAst, actualAst); |
| stream.close(); |
| } |
| } |
| |
| /** |
| * Tests the validation by comparing the validated template. |
| * |
| * @throws FileNotFoundException |
| * if the file can't be found |
| * @throws IOException |
| * if the given stream can't be written to |
| */ |
| @Test |
| public void validation() throws FileNotFoundException, IOException { |
| AcceleoValidator validator = new AcceleoValidator(environment, environment.getQueryEnvironment() |
| .getLookupEngine()); |
| final List<IValidationMessage> messages = validator.validate(astResult, qualifiedName) |
| .getValidationMessages(); |
| final String actualContent = getValidationContent(messages); |
| final File expectedFile = getExpectedValidatedFile(new File(testFolderPath)); |
| final File actualFile = getActualValidatedFile(new File(testFolderPath)); |
| |
| if (!expectedFile.exists()) { |
| if (!actualFile.exists() && !expectedFile.exists()) { |
| actualFile.createNewFile(); |
| } |
| try (FileOutputStream stream = new FileOutputStream(actualFile);) { |
| setContent(stream, UTF_8, actualContent); |
| } |
| fail("file doesn't exist."); |
| } else { |
| String expectedContent = ""; |
| try (FileInputStream stream = new FileInputStream(expectedFile);) { |
| expectedContent = getContent(stream, UTF_8); |
| } |
| assertEquals(expectedContent, actualContent); |
| } |
| } |
| |
| /** |
| * Serializes the given {@link List} of {@link IValidationMessage}. |
| * |
| * @param messages |
| * the {@link List} of {@link IValidationMessage} |
| * @return the string representation of the given {@link List} of {@link IValidationMessage |
| */ |
| private String getValidationContent(List<IValidationMessage> messages) { |
| final String res; |
| |
| if (!messages.isEmpty()) { |
| final StringBuilder builder = new StringBuilder(); |
| for (IValidationMessage message : messages) { |
| builder.append(message.getLevel()); |
| builder.append(" "); |
| builder.append(message.getMessage()); |
| builder.append(" - "); |
| builder.append(message.getStartPosition()); |
| builder.append(" "); |
| builder.append(message.getEndPosition()); |
| builder.append(DEFAULT_END_OF_LINE_CHARACTER); |
| } |
| res = builder.substring(0, builder.length() - 1); |
| } else { |
| res = ""; |
| } |
| |
| return res; |
| } |
| |
| /** |
| * Gets the module file from the test folder path. |
| * |
| * @param testFolder |
| * the test folder path |
| * @return the module file from the test folder path |
| */ |
| protected final File getModuleFile(File testFolder) { |
| return getTemplateFileInternal(testFolder); |
| } |
| |
| /** |
| * Gets the expected AST file from the test folder path. |
| * |
| * @param testFolder |
| * the test folder path |
| * @return the expected AST file from the test folder path |
| */ |
| protected File getFragmentsFolder(File testFolder) { |
| return new File(testFolder + File.separator + "fragments"); |
| } |
| |
| /** |
| * Gets the expected AST file from the test folder path. |
| * |
| * @param testFolder |
| * the test folder path |
| * @return the expected AST file from the test folder path |
| */ |
| protected File getExpectedASTFile(File testFolder) { |
| return new File(testFolder + File.separator + testFolder.getName() + "-expected-ast.txt"); |
| } |
| |
| /** |
| * Gets the actual AST file from the test folder path. |
| * |
| * @param testFolder |
| * the test folder path |
| * @return the actual AST file from the test folder path |
| */ |
| protected File getActualASTFile(File testFolder) { |
| return new File(testFolder + File.separator + testFolder.getName() + "-actual-ast.txt"); |
| } |
| |
| /** |
| * Gets the expected validated template file from the test folder path. |
| * |
| * @param testFolder |
| * the test folder path |
| * @return the expected template file from the test folder path |
| */ |
| protected File getExpectedValidatedFile(File testFolder) { |
| return new File(testFolder + File.separator + testFolder.getName() + "-expected-validation.txt"); |
| } |
| |
| /** |
| * Gets the actual validated template file from the test folder path. |
| * |
| * @param testFolder |
| * the test folder path |
| * @return the actual template file from the test folder path |
| */ |
| protected File getActualValidatedFile(File testFolder) { |
| return new File(testFolder + File.separator + testFolder.getName() + "-actual-validation.txt"); |
| } |
| |
| /** |
| * Gets the actual validated template file from the test folder path. |
| * |
| * @param testFolder |
| * the test folder path |
| * @return the actual template file from the test folder path |
| */ |
| protected File getExpectedRuntimeMessageFile(File testFolder) { |
| return new File(testFolder + File.separator + testFolder.getName() + "-expected-runtimeMessages.txt"); |
| } |
| |
| /** |
| * Gets the actual validated template file from the test folder path. |
| * |
| * @param testFolder |
| * the test folder path |
| * @return the actual template file from the test folder path |
| */ |
| protected File getActualRuntimeMessageFile(File testFolder) { |
| return new File(testFolder + File.separator + testFolder.getName() + "-actual-runtimeMessages.txt"); |
| } |
| |
| /** |
| * Gets the expected generated file from the test folder path. |
| * |
| * @param testFolder |
| * the test folder path |
| * @return the expected template file from the test folder path |
| */ |
| protected File getExpectedGeneratedFile(File testFolder) { |
| return new File(testFolder + File.separator + testFolder.getName() + "-expected-generation.txt"); |
| } |
| |
| /** |
| * Gets the actual generated file from the test folder path. |
| * |
| * @param testFolder |
| * the test folder path |
| * @return the actual template file from the test folder path |
| */ |
| protected File getActualGeneratedFile(File testFolder) { |
| return new File(testFolder + File.separator + testFolder.getName() + "-actual-generation.txt"); |
| } |
| |
| /** |
| * Gets the model file from the test folder path. |
| * |
| * @param testFolder |
| * the test folder path |
| * @return the model file from the test folder path |
| */ |
| protected File getModelFile(File testFolder) { |
| return new File(testFolder + File.separator + testFolder.getName() + ".xmi"); |
| } |
| |
| /** |
| * Gets the template file from the test folder path. |
| * |
| * @param testFolder |
| * the test folder path |
| * @return the template file from the test folder path |
| */ |
| private static File getTemplateFileInternal(File testFolder) { |
| return new File(testFolder + File.separator + testFolder.getName() + ".mtl"); |
| } |
| |
| /** |
| * Gets the {@link Collection} of test folders from the given folder path. |
| * |
| * @param folderPath |
| * the folder path |
| * @return the {@link Collection} of test folders from the given folder path |
| */ |
| public static Collection<Object[]> retrieveTestFolders(String folderPath) { |
| Collection<Object[]> parameters = new ArrayList<Object[]>(); |
| |
| File folder = new File(folderPath); |
| final File[] children = folder.listFiles(new FileFilter() { |
| |
| @Override |
| public boolean accept(File pathname) { |
| final boolean res; |
| |
| if (pathname.isDirectory() && pathname.canRead()) { |
| res = getTemplateFileInternal(pathname).exists(); |
| } else { |
| res = false; |
| } |
| |
| return res; |
| } |
| |
| }); |
| Arrays.sort(children); |
| for (File child : children) { |
| parameters.add(new Object[] {child.getPath() }); |
| } |
| |
| return parameters; |
| } |
| |
| /** |
| * Gets the content of the given {@link InputStream}. |
| * |
| * @param stream |
| * the {@link InputStream} |
| * @param charsetName |
| * The name of a supported {@link java.nio.charset.Charset </code>charset<code>} |
| * @return a {@link CharSequence} of the content of the given {@link InputStream} |
| * @throws IOException |
| * if the {@link InputStream} can't be read |
| */ |
| public static String getContent(InputStream stream, String charsetName) throws IOException { |
| final int len = 8192; |
| StringBuilder res = new StringBuilder(len); |
| if (len != 0) { |
| try (InputStreamReader input = new InputStreamReader(new BufferedInputStream(stream), |
| charsetName)) { |
| char[] buffer = new char[len]; |
| int length = input.read(buffer); |
| while (length != -1) { |
| res.append(buffer, 0, length); |
| length = input.read(buffer); |
| } |
| input.close(); |
| } |
| } |
| return res.toString(); |
| } |
| |
| /** |
| * Sets the given content to the given {@link OutputStream}. |
| * |
| * @param stream |
| * the {@link OutputStream} |
| * @param charsetName |
| * the charset name |
| * @param content |
| * the content to write |
| * @throws UnsupportedEncodingException |
| * if the given charset is not supported |
| * @throws IOException |
| * if the given stream can't be written to |
| */ |
| public static void setContent(OutputStream stream, String charsetName, String content) |
| throws UnsupportedEncodingException, IOException { |
| stream.write(content.getBytes(charsetName)); |
| stream.flush(); |
| } |
| |
| } |