/* * Copyright (c) 2012, the Dart project authors. * * Licensed under the Eclipse Public License v1.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at * * http://www.eclipse.org/legal/epl-v10.html * * Unless required by applicable law or agreed to in writing, software distributed under the License * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express * or implied. See the License for the specific language governing permissions and limitations under * the License. */ package com.google.dart.tools.core.test.util; import com.google.dart.engine.utilities.io.PrintStringWriter; import junit.framework.Assert; import junit.framework.ComparisonFailure; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IResource; import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.FileLocator; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.Path; import org.eclipse.core.runtime.Platform; import org.w3c.dom.Document; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.xml.sax.SAXException; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.StringReader; import java.net.URL; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; /** * The class <code>TestUtilities</code> defines utility methods that can be used when writing tests. */ public class TestUtilities { /** * The interface <code>ThreadController</code> defines the behavior of objects used by the * {@link #wait(long, ThreadController) wait} method. */ public interface ThreadController { public void startThread(); public boolean threadCompleted(); } public static final String CORE_TEST_PLUGIN_ID = "com.google.dart.tools.core_test"; /** * The name of the directory containing projects that can be loaded for testing purposes. */ public static final String PROJECT_DIRECTORY_NAME = "test_data"; //$NON-NLS-1$ /** * Assert that the actual array contains the same number of elements as the expected value and * that every corresponding pair of elements are equal. * * @param expected an array containing the expected values * @param actual an array containing the actual values */ public static void assertEqualElements(Object[] expected, Object[] actual) { int length = expected.length; Assert.assertEquals(length, actual.length); for (int i = 0; i < length; i++) { Assert.assertEquals(expected[i], actual[i]); } } /** * Assert that the array of actual values contains every value in the array of expected values, * allowing the values to occur in a different order in the two arrays. For example, the following * would not throw an exception: <blockquote><code> * assertEqualsIgnoreOrder(new String[] {"a", "b", "c"}, new String[] {"c", "a", "b"}); * </code></blockquote> while the following would: <blockquote><code> * assertEqualsIgnoreOrder(new String[] {"a", "b", "a"}, new String[] {"a", "a", "a"}); * </code></blockquote> * * @param expected an array containing the expected values * @param actual an array containing the actual values */ public static void assertEqualsIgnoreOrder(Object[] expected, Object[] actual) { int length = expected.length; if (actual.length != length) { Assert.fail("Expected length of " + length + " found length of " + actual.length); } boolean[] found = new boolean[length]; for (int i = 0; i < length; i++) { int index = findUnfound(expected[i], actual, found); if (index < 0) { Assert.fail("Expected to find " + expected[i] + ", but did not"); } found[index] = true; } } /** * Assert that two strings are equal if whitespace characters are ignored. * * @param expected the expected value of the string * @param actual the actual value of the string */ public static void assertEqualsIgnoreWhitespace(String expected, String actual) { assertEqualsIgnoreWhitespace(null, expected, actual); } /** * Assert that two strings are equal if whitespace characters are ignored. * * @param message the message to be included if the strings are not equal * @param expected the expected value of the string * @param actual the actual value of the string */ public static void assertEqualsIgnoreWhitespace(String message, String expected, String actual) { if (expected == null) { if (actual == null) { return; } throw new ComparisonFailure(message, expected, actual); } else if (actual == null) { throw new ComparisonFailure(message, expected, actual); } StringReader expectedReader = new StringReader(expected); StringReader actualReader = new StringReader(actual); int expectedChar = getNextNonWhitespace(expectedReader); int actualChar = getNextNonWhitespace(actualReader); while (expectedChar == actualChar) { if (expectedChar < 0) { return; } expectedChar = getNextNonWhitespace(expectedReader); actualChar = getNextNonWhitespace(actualReader); } throw new ComparisonFailure(message, expected, actual); } /** * Assert that the given object is an instance of the given class. This is equivalent to * <code>assertTrue(object instanceof type)</code> but with a more meaningful failure message. * * @param object the object whose type is being tested * @param type the type that the object must be an instance of */ public static void assertInstanceof(Object object, Class<?> type) { if (!type.isInstance(object)) { Assert.fail("Expected instanceof " + type.getName() + " but was instanceof " + object.getClass().getName()); } } /** * Assert that the Eclipse .log file does not exist. */ public static void assertNoLogFile() { File logFile = getLogFile(); if (logFile.exists()) { PrintStringWriter writer = new PrintStringWriter(); try { String contents = FileUtilities.getContents(logFile); writer.println("Non-empty log file. Log file contents:"); writer.println(); writer.print(contents); } catch (IOException exception) { writer.println("Non-empty log file. Could not access contents of log file."); writer.println(); exception.printStackTrace(writer); } Assert.fail(writer.toString()); } } /** * Copy the content in the directory with the given name located in the <code>test_data</code> * directory in core test plug-in into the specified target directory. * * @param projectName the name of the directory containing the project * @param targetDirectory the directory into which the content is copied. This directory is * created if it does not already exist * @throws IOException if a required file cannot be accessed */ public static void copyPluginRelativeContent(String projectName, File targetDirectory) throws IOException { copyPluginRelativeContent(CORE_TEST_PLUGIN_ID, projectName, targetDirectory); } /** * Copy the content in the directory with the given name located in the <code>test_data</code> * directory in the plug-in with the given id into the specified target directory. * * @param pluginId the id of the plug-in containing the project * @param projectName the name of the directory containing the project * @param targetDirectory the directory into which the content is copied. This directory is * created if it does not already exist * @throws IOException if a required file cannot be accessed */ public static void copyPluginRelativeContent(String pluginId, String projectName, File targetDirectory) throws IOException { URL pluginInstallUri = PluginUtilities.getInstallUrl(pluginId); URL sourceUrl = new URL(pluginInstallUri, PROJECT_DIRECTORY_NAME + "/" + projectName); IPath sourcePath = new Path(FileLocator.toFileURL(sourceUrl).getPath()); FileUtilities.copyDirectoryContents(sourcePath.toFile(), targetDirectory); } /** * Create a new directory that can safely be deleted after the test has completed. * * @return the directory that was created */ public static File createTempDirectory() { File tempDir = new File(System.getProperty("java.io.tmpdir")); //$NON-NLS-1$ File subdirectory = new File(tempDir, "test"); //$NON-NLS-1$ int index = 0; while (subdirectory.exists()) { index = index + 1; subdirectory = new File(tempDir, "test" + index); //$NON-NLS-1$ } subdirectory.mkdirs(); return subdirectory; } /** * Cause this thread to wait for at least the given number of milliseconds. * * @param duration the number of milliseconds that this thread should wait */ public static void delay(long duration) { // Display display = Display.getCurrent(); // if (display == null) { try { Thread.sleep(duration); } catch (InterruptedException exception) { // Ignored } // } else { // long endTime = System.currentTimeMillis() + duration; // while (System.currentTimeMillis() < endTime) { // if (!display.readAndDispatch()) { // display.sleep(); // } // display.update(); // } // } } /** * Delete the Eclipse .log file if it exists. */ public static void deleteLogFile() { File logFile = getLogFile(); if (logFile.exists()) { logFile.delete(); } } /** * Call project.delete() in a loop. If we get a failure, run a GC to try and clean up dangling * references to the files. Bail out after MAX_FAILURES tries. * <p> * This utility method exists because of issues deleting resources in windows when there are open * file handles to those resources. * * @param project the project to delete * @throws CoreException if an exception occurred while deleting the project */ public static void deleteProject(IProject project) throws CoreException { final int MAX_FAILURES = 10; int failureCount = 0; while (true) { try { project.delete(true, true, null); return; } catch (CoreException ce) { failureCount++; if (failureCount >= MAX_FAILURES) { throw ce; } Runtime.getRuntime().gc(); Runtime.getRuntime().runFinalization(); } } } /** * Return the Eclipse .log file. * * @return the Eclipse .log file */ public static File getLogFile() { return Platform.getLogFileLocation().toFile(); } /** * Return the absolute path to the resource within the specified plug-in with the given relative * path. * * @param pluginId the id of the plug-in containing the resource * @param relativePath the relative path of the resource within the project * @return the absolute path to the resource * @throws IOException if some portion of the path is invalid */ public static IPath getPluginRelativePath(String pluginId, IPath relativePath) throws IOException { IPath pluginPath = new Path( FileLocator.toFileURL(PluginUtilities.getInstallUrl(pluginId)).getPath()); return pluginPath.append(relativePath); } /** * Return the name of the project at the given directory. * * @param projectRootDirectory the root directory of the project * @return the name of the project at the given directory * @throws FileNotFoundException if a required file or directory does not exist * @throws IOException if a required file cannot be accessed * @throws SAXException if the .project file is not valid * @throws XMLException if the .project file is not valid */ public static String getProjectName(IPath projectRootDirectory) throws FileNotFoundException, IOException, SAXException { File projectFile = projectRootDirectory.append(".project").toFile(); try { Document projectDocument = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse( projectFile); NodeList children = projectDocument.getFirstChild().getChildNodes(); int count = children.getLength(); for (int i = 0; i < count; i++) { Node node = children.item(i); if (node.getNodeName().equals("name")) { return node.getTextContent(); } } throw new SAXException("Missing name element in project file"); } catch (ParserConfigurationException exception) { throw new SAXException("Missing name element in project file"); } } /** * Return <code>true</code> if the Eclipse .log file does not exist. * * @return <code>true</code> if the Eclipse .log file does not exist */ public static boolean logFileIsEmpty() { return !getLogFile().exists(); } /** * Refresh the content of the workspace to be consistent with what's on disk. */ public static void refreshWorkspace() { try { ResourcesPlugin.getWorkspace().getRoot().refreshLocal(IResource.DEPTH_INFINITE, null); } catch (CoreException exception) { // DartCore.getLogger().logError(exception, "Could not refresh workspace"); //$NON-NLS-1$ } } /** * Run the given operation, passing in a temporary directory that is newly created (implying that * it exists and is empty), and delete the directory after the operation has completed. * * @param operation the operation to be run * @throws Exception if the operation throws an Exception or if the directory either cannot be * created or cannot be deleted */ public static void runWithTempDirectory(FileOperation operation) throws Exception { File tempDir = createTempDirectory(); try { operation.run(tempDir); } finally { FileUtilities.delete(tempDir); } } /** * Use the given controller to perform some operation on a separate thread, then wait until either * the controller indicates that the thread has completed or until the given number of * milliseconds have passed. * * @param timeout the maximum number of milliseconds that this method will wait for the thread to * complete before returning (or a negative value if there is no timeout) * @param controller the controller used to start the thread and to determine whether the thread * has finished running */ public static void wait(long timeout, ThreadController controller) { if (timeout < 0) { controller.startThread(); while (!controller.threadCompleted()) { try { Thread.sleep(10); } catch (InterruptedException exception) { // Ignored } } } else { long endTime = System.currentTimeMillis() + timeout; controller.startThread(); while (!controller.threadCompleted() && System.currentTimeMillis() < endTime) { try { Thread.sleep(10); } catch (InterruptedException exception) { // Ignored } } } } private static int findUnfound(Object object, Object[] actual, boolean[] found) { if (object == null) { for (int i = 0; i < actual.length; i++) { if (!found[i] && actual[i] == null) { return i; } } } else { for (int i = 0; i < actual.length; i++) { if (!found[i] && object.equals(actual[i])) { return i; } } } return -1; } private static int getNextNonWhitespace(StringReader reader) { try { int nextChar = reader.read(); while (nextChar >= 0 && Character.isWhitespace((char) nextChar)) { nextChar = reader.read(); } return nextChar; } catch (IOException exception) { // This cannot happen because we're reading from a StringReader. return -1; } } /** * Prevent the creation of instances of this class. */ private TestUtilities() { super(); } }