/** * Copyright (c) 2004-2007 IBM Corporation 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: * IBM - Initial API and implementation */ package org.eclipse.emf.test.tools.merger; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import java.io.File; import java.io.FileInputStream; import java.io.FilenameFilter; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.Hashtable; import java.util.Map; import java.util.regex.Pattern; import org.eclipse.emf.codegen.ecore.generator.Generator; import org.eclipse.emf.codegen.ecore.generator.GeneratorAdapterFactory; import org.eclipse.emf.codegen.ecore.genmodel.GenModel; import org.eclipse.emf.codegen.ecore.genmodel.GenModelFactory; import org.eclipse.emf.codegen.ecore.genmodel.generator.GenModelGeneratorAdapterFactory; import org.eclipse.emf.codegen.merge.java.JControlModel; import org.eclipse.emf.codegen.merge.java.JMerger; import org.eclipse.emf.codegen.merge.java.facade.FacadeHelper; import org.eclipse.emf.common.EMFPlugin; import org.eclipse.emf.test.common.TestUtil; import org.eclipse.emf.test.tools.AllSuites; import org.eclipse.jdt.core.JavaCore; import org.eclipse.jdt.core.formatter.DefaultCodeFormatterConstants; import org.junit.Before; import org.junit.ComparisonFailure; import org.junit.Test; /** * Base class for all JMerger tests. * <p> * For special test cases, default data directory is determined by {@link #getDefaultDataDirectory()}. * For tests created by <code>JMergerTestSuite</code>, see {@link JMergerTestSuite}. * <p> * Merge source and target files are respectively <code>MergerSource.java</code> and <code>MergerTarget.java</code> in data directory. * <p> * Expected output file is either test specific output file (determined by {@link #getTestSpecificExpectedOutput()} or * <code>MergerExpected.java</code> in data directory. * <p> * Merge rules are used from <code>merge.xml</code> file in data directory if it exists, otherwise default EMF merge rules are used. */ public abstract class JMergerTest { /** * Default root data directory containing java versions subdirectories. * <p> * Default is /data/merge.input * @see JMergerTest#DIRECTORY_NAMES_TO_JAVA_VERSIONS */ protected static final File DEFAULT_ROOT_DIRECTORY = new File(TestUtil.getPluginDirectory(AllSuites.PLUGIN_ID) + File.separator + "data" + File.separator + "merge.input"); /** * Default name of the expected output file. */ public static final String DEFAULT_EXPECTED_OUTPUT_FILENAME = "MergerExpected.java"; /** * Map of directory names to versions of Java to be used to overwrite settings in JavaCore. */ public static final Map<String, String> DIRECTORY_NAMES_TO_JAVA_VERSIONS; static { Map<String, String> directoryNamesMap = new HashMap<String, String>(4); directoryNamesMap.put("java1.4", JavaCore.VERSION_1_4); directoryNamesMap.put("java5", JavaCore.VERSION_1_5); DIRECTORY_NAMES_TO_JAVA_VERSIONS = Collections.unmodifiableMap(directoryNamesMap); } public static Collection<Object[]> parameters(String prefix) { return populateSuite(prefix); } /** * Verifies that target contents matches the contents of expected output file. * * @param expectedOutput * @param targetContents */ protected static void verifyMerge(File expectedOutput, String targetContents) { // extract merged contents StringBuilder mergeResult = new StringBuilder(targetContents); // The merge expected file was saved without any '\r' so // we need to remove it from the mergedResult for (int i = mergeResult.length() - 1; i >= 0; i--) { if ('\r' == mergeResult.charAt(i)) { mergeResult.deleteCharAt(i); } } String expectedMerge = TestUtil.readFile(expectedOutput, false); String actualMerge = mergeResult.toString(); try { assertEquals("Make sure the line breaks are OK. The expected merge should have no '\\r'", expectedMerge, actualMerge); } catch (ComparisonFailure exception) { File alternative = new File(expectedOutput.toString().replace(".java", "Alt.java")); if (alternative.exists()) { expectedMerge = TestUtil.readFile(alternative, false); assertEquals("Make sure the line breaks are OK. The expected merge should have no '\\r'", expectedMerge, actualMerge); } else { throw exception; } } } /** * If <code>true</code>, editor options are set from default options in genmodel. */ protected boolean applyGenModelEditorFormatting = false; protected File dataDirectory = null; protected File expectedOutput = null; /** * URI of merge rules file to be used by {@link JControlModel#initialize(FacadeHelper, String)} */ protected String mergeRulesURI = null; protected File source = null; protected File target = null; public JMergerTest(File dataDirectory) { this.dataDirectory = dataDirectory; } /** * @param javaVersion (one of {@link JavaCore#VERSION_1_1} to {@link JavaCore#VERSION_1_6} */ @SuppressWarnings({"unchecked", "rawtypes"}) protected void adjustSourceCompatibility(String javaVersion) { Hashtable map = JavaCore.getOptions(); map.put(JavaCore.COMPILER_SOURCE, javaVersion); JavaCore.setOptions(map); } /** * Adjusts JavaCore source compatibility options based on {@link #computeJavaVersion()}. * * @see #adjustSourceCompatibility(String) */ protected void adjustSourceCompatibility() { String javaVersion = computeJavaVersion(); assertNotNull("Unable to determine Java version from directory name. ", javaVersion); adjustSourceCompatibility(javaVersion); } /** * Determines java version based on the name of the parent of data directory. * <p> * Parent directory must match one of {@link #DIRECTORY_NAMES_TO_JAVA_VERSIONS} * @return java version or <code>null</code> if can not be determined */ protected String computeJavaVersion() { return computeJavaVersion(dataDirectory); } protected static String computeJavaVersion(File directory) { File parentDirectory = directory; String javaVersion = null; do { javaVersion = DIRECTORY_NAMES_TO_JAVA_VERSIONS.get(parentDirectory.getName()); parentDirectory = parentDirectory.getParentFile(); } while (javaVersion == null && parentDirectory != null); return javaVersion; } /** * @return test specific expected output file or default expected output file, but never <code>null</code>. * @see #getTestSpecificExpectedOutput() */ protected File computeExpectedOutputFile() { File expectedOutput = getTestSpecificExpectedOutput(); if (expectedOutput != null && expectedOutput.exists()) { return expectedOutput; } else { return new File(dataDirectory, DEFAULT_EXPECTED_OUTPUT_FILENAME).getAbsoluteFile(); } } /** * Returns unique name for the expected output file. * <p> * Expected to be overwritten by subclasses. * <p> * This implementation returns <code>null</code>. * @return expected output file, or <code>null</code> if only default file should be used */ protected File getTestSpecificExpectedOutput() { return null; } /** * @param jControlModel */ @SuppressWarnings("rawtypes") protected void applyGenModelEditorFormattingSettings(org.eclipse.emf.codegen.merge.java.JControlModel jControlModel) { if (EMFPlugin.IS_ECLIPSE_RUNNING) { Map options = JavaCore.getOptions(); String tabSize = (String)options.get(DefaultCodeFormatterConstants.FORMATTER_TAB_SIZE); String braceStyle = (String)options.get(DefaultCodeFormatterConstants.FORMATTER_BRACE_POSITION_FOR_TYPE_DECLARATION); String tabCharacter = (String)options.get(DefaultCodeFormatterConstants.FORMATTER_TAB_CHAR); if (JavaCore.TAB.equals(tabCharacter)) { jControlModel.setLeadingTabReplacement("\t"); } else { String spaces = ""; for (int i = Integer.parseInt(tabSize); i > 0; --i) { spaces += " "; } jControlModel.setLeadingTabReplacement(spaces); } jControlModel.setConvertToStandardBraceStyle(DefaultCodeFormatterConstants.END_OF_LINE.equals(braceStyle)); } } /** * @return URI of EMF merge rules resource */ protected String getEMFMergeRulesURI() { // create model GenModel genModel = GenModelFactory.eINSTANCE.createGenModel(); // create adapter factory GeneratorAdapterFactory adapterFactory = GenModelGeneratorAdapterFactory.DESCRIPTOR.createAdapterFactory(); adapterFactory.setGenerator(new Generator()); adapterFactory.initialize(genModel); // get merge rules URI return adapterFactory.getGenerator().getOptions().mergeRulesURI; } /** * @return the expectedOutput */ public File getExpectedOutput() { return expectedOutput; } /** * @return the mergeRulesURI */ public String getMergeRulesURI() { return mergeRulesURI; } /** * @param expectedOutput the expectedOutput to set */ public void setExpectedOutput(File expectedOutput) { this.expectedOutput = expectedOutput; } /** * @param mergeRulesURI the mergeRulesURI to set */ public void setMergeRulesURI(String mergeRulesURI) { this.mergeRulesURI = mergeRulesURI; } /** * Tests whether the facade helper is of correct type * @param facadeHelper */ protected abstract void instanceTest(FacadeHelper facadeHelper); /** * @return facade helper instance */ protected abstract FacadeHelper instanciateFacadeHelper(); /** * Perform and verify merge. To be used in merge tests by subclasses. * <b> * Before performing merge, java compiler source version is set based on data directory. * * @throws Exception * @see #adjustSourceCompatibility() */ @Test public void merge() throws Exception { adjustSourceCompatibility(); verifyMerge(expectedOutput, mergeFiles()); } /** * @return contents after merging contents of source and target files * @throws Exception */ protected String mergeFiles() throws Exception { String sourceCompatibility = JavaCore.getOption(JavaCore.COMPILER_SOURCE); FacadeHelper facadeHelper = instanciateFacadeHelper(); instanceTest(facadeHelper); JControlModel controlModel = new JControlModel(); assertFalse(controlModel.canMerge()); controlModel.initialize(facadeHelper, mergeRulesURI); if (applyGenModelEditorFormatting) { applyGenModelEditorFormattingSettings(controlModel); } assertTrue(controlModel.canMerge()); JMerger jMerger = new JMerger(controlModel); // set source jMerger.setSourceCompilationUnit(jMerger.createCompilationUnitForContents(TestUtil.readFile(source, true))); // set target if (target.isFile()) { jMerger.setTargetCompilationUnit(jMerger.createCompilationUnitForInputStream(new FileInputStream(target))); } // merge source and target jMerger.merge(); // Ensure the facade is returning the COMPILER_SOURCE to the original value. assertEquals(sourceCompatibility, JavaCore.getOption(JavaCore.COMPILER_SOURCE)); return jMerger.getTargetCompilationUnitContents(); } /** * Sets up data directory, source, target, expected output, and merge rules attributes. * <p> * If <code>merge.xml</code> is not available in data directory, default EMF merge rules are used. * * @see #getDataDirectory() * @see #computeExpectedOutputFile() * @see #getEMFMergeRulesURI() */ @Before public void setUp() throws Exception { assertTrue("Merge Data directory is not available - " + dataDirectory.getAbsolutePath(), dataDirectory.isDirectory()); if (mergeRulesURI == null) { File mergeXML = new File(dataDirectory, "merge.xml").getAbsoluteFile(); if (!mergeXML.exists()) { mergeRulesURI = getEMFMergeRulesURI(); } else { mergeRulesURI = mergeXML.getAbsolutePath(); } } source = new File(dataDirectory, "MergerSource.java").getAbsoluteFile(); assertTrue("Merge Source file is not available - " + source.getAbsolutePath(), source.isFile()); target = new File(dataDirectory, "MergerTarget.java").getAbsoluteFile(); if (expectedOutput == null) { expectedOutput = computeExpectedOutputFile(); } assertTrue("Merge Result file is not available - " + expectedOutput.getAbsolutePath(), expectedOutput.isFile()); } /** * Filter for the directories that will be used as data directories for tests. */ protected static class JMergerDataDirectoryFilter implements FilenameFilter { protected static final Pattern EXCLUDE_DATA_DIRECTORY_NAME_PATTERN = Pattern.compile("^(?:CVS)|(?:\\..*)$"); protected static final Pattern INCLUDE_DATA_DIRECTORY_NAME_PATTERN = Pattern.compile(".*"); // use something like this line to restrict the test // protected static final Pattern INCLUDE_DATA_DIRECTORY_NAME_PATTERN = Pattern.compile("(bugzilla|178183)"); public boolean accept(File dir, String name) { // must be a directory and must match the pattern File dataDirectoryCandidate = new File(dir, name); return dataDirectoryCandidate.isDirectory() && !EXCLUDE_DATA_DIRECTORY_NAME_PATTERN.matcher(name).matches() && INCLUDE_DATA_DIRECTORY_NAME_PATTERN.matcher(name).matches(); } } private static final FilenameFilter FILTER = new JMergerDataDirectoryFilter(); /** * Populates suite with test cases for each data directory. * @param prefix */ private static Collection<Object[]> populateSuite(String prefix) { assertTrue("Directory " + DEFAULT_ROOT_DIRECTORY.getAbsolutePath() + " does not exist.", DEFAULT_ROOT_DIRECTORY.exists()); assertTrue("Directory " + DEFAULT_ROOT_DIRECTORY.getAbsolutePath() + " is not a directory.", DEFAULT_ROOT_DIRECTORY.isDirectory()); Collection<Object[]> tests = new ArrayList<Object[]>(); // Loop for all directories of all java versions. for (String javaVersionDirectoryName : JMergerTest.DIRECTORY_NAMES_TO_JAVA_VERSIONS.keySet()) { File dataDirsDirectory = new File(DEFAULT_ROOT_DIRECTORY, javaVersionDirectoryName); if (dataDirsDirectory.isDirectory()) { createTestSuiteRecursively(prefix, tests, dataDirsDirectory, ""); } } assertFalse("Subdirectories " + JMergerTest.DIRECTORY_NAMES_TO_JAVA_VERSIONS.keySet().toString() + " under " + DEFAULT_ROOT_DIRECTORY.getAbsolutePath() + " must contain subdirectories with source, target and output files.", tests.isEmpty()); return tests; } /** * Creates a test suite recursively for all directories in the directory tree. * Directories used as input must be accepted by {@link JMergerDataDirectoryFilter}. * @param prefix * @param rootDirectory root directory to create test suite for * @return resulting test suite */ private static void createTestSuiteRecursively(String prefix, Collection<Object[]> result, File rootDirectory, String subDirectory) { // If there are no test cases for this directory, try to create test suites from subdirectories. Object[] test = createSingleInputTestSuite(prefix, rootDirectory, subDirectory); if (test == null) { String[] dataDirectoriesNames = new File(rootDirectory, subDirectory).list(FILTER); if (dataDirectoriesNames.length > 0) { Arrays.sort(dataDirectoriesNames); for (String dataDirectoryName : dataDirectoriesNames) { String subSubDirectory = subDirectory.length() == 0 ? dataDirectoryName : subDirectory + "/" + dataDirectoryName; File dataDirectory = new File(rootDirectory, subSubDirectory); if (!prefix.equals("JDOM") || !"1.5".equals(computeJavaVersion(dataDirectory))) { createTestSuiteRecursively(prefix, result, rootDirectory, subSubDirectory); } } } } else { result.add(test); } } /** * Creates and returns test suite for a single input directory, or null if it's not appropriate for the prefix. */ private static Object[] createSingleInputTestSuite(String prefix, File rootDirectory, String subDirectory) { File dataDirectory = new File(rootDirectory, subDirectory); if (new File(dataDirectory, "MergerExpected.java").isFile() || new File(dataDirectory, prefix + "MergerExpected.java").isFile()) { return new Object[] { subDirectory, dataDirectory }; } else { return null; } } }