| /******************************************************************************* |
| * Copyright (c) 2000, 2009 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 Corporation - initial API and implementation |
| * Achim Demelt <a.demelt@exxcellent.de> - [junit] Separate UI from non-UI code - https://bugs.eclipse.org/bugs/show_bug.cgi?id=278844 |
| *******************************************************************************/ |
| |
| package org.eclipse.jdt.internal.junit.model; |
| |
| import java.io.File; |
| import java.io.FileOutputStream; |
| import java.io.IOException; |
| import java.io.OutputStream; |
| import java.util.ArrayList; |
| import java.util.HashSet; |
| import java.util.Iterator; |
| import java.util.LinkedList; |
| import java.util.List; |
| |
| import javax.xml.parsers.ParserConfigurationException; |
| import javax.xml.parsers.SAXParser; |
| import javax.xml.parsers.SAXParserFactory; |
| import javax.xml.transform.OutputKeys; |
| import javax.xml.transform.Transformer; |
| import javax.xml.transform.TransformerConfigurationException; |
| import javax.xml.transform.TransformerException; |
| import javax.xml.transform.TransformerFactory; |
| import javax.xml.transform.TransformerFactoryConfigurationError; |
| import javax.xml.transform.sax.SAXSource; |
| import javax.xml.transform.stream.StreamResult; |
| |
| import org.xml.sax.InputSource; |
| import org.xml.sax.SAXException; |
| |
| import org.eclipse.core.runtime.Assert; |
| import org.eclipse.core.runtime.CoreException; |
| import org.eclipse.core.runtime.IStatus; |
| import org.eclipse.core.runtime.ListenerList; |
| import org.eclipse.core.runtime.Platform; |
| |
| import org.eclipse.debug.core.DebugPlugin; |
| import org.eclipse.debug.core.ILaunch; |
| import org.eclipse.debug.core.ILaunchConfiguration; |
| import org.eclipse.debug.core.ILaunchListener; |
| import org.eclipse.debug.core.ILaunchManager; |
| |
| import org.eclipse.jdt.core.IJavaProject; |
| |
| import org.eclipse.jdt.internal.junit.BasicElementLabels; |
| import org.eclipse.jdt.internal.junit.JUnitCorePlugin; |
| import org.eclipse.jdt.internal.junit.JUnitPreferencesConstants; |
| import org.eclipse.jdt.internal.junit.Messages; |
| import org.eclipse.jdt.internal.junit.launcher.JUnitLaunchConfigurationConstants; |
| import org.eclipse.jdt.internal.junit.model.TestElement.Status; |
| |
| /** |
| * Central registry for JUnit test runs. |
| */ |
| public final class JUnitModel { |
| |
| private final class JUnitLaunchListener implements ILaunchListener { |
| |
| /** |
| * Used to track new launches. We need to do this |
| * so that we only attach a TestRunner once to a launch. |
| * Once a test runner is connected, it is removed from the set. |
| */ |
| private HashSet fTrackedLaunches= new HashSet(20); |
| |
| /* |
| * @see ILaunchListener#launchAdded(ILaunch) |
| */ |
| public void launchAdded(ILaunch launch) { |
| fTrackedLaunches.add(launch); |
| } |
| |
| /* |
| * @see ILaunchListener#launchRemoved(ILaunch) |
| */ |
| public void launchRemoved(final ILaunch launch) { |
| fTrackedLaunches.remove(launch); |
| //TODO: story for removing old test runs? |
| // getDisplay().asyncExec(new Runnable() { |
| // public void run() { |
| // TestRunnerViewPart testRunnerViewPart= findTestRunnerViewPartInActivePage(); |
| // if (testRunnerViewPart != null && testRunnerViewPart.isCreated() && launch.equals(testRunnerViewPart.getLastLaunch())) |
| // testRunnerViewPart.reset(); |
| // } |
| // }); |
| } |
| |
| /* |
| * @see ILaunchListener#launchChanged(ILaunch) |
| */ |
| public void launchChanged(final ILaunch launch) { |
| if (!fTrackedLaunches.contains(launch)) |
| return; |
| |
| ILaunchConfiguration config= launch.getLaunchConfiguration(); |
| if (config == null) |
| return; |
| |
| final IJavaProject javaProject= JUnitLaunchConfigurationConstants.getJavaProject(config); |
| if (javaProject == null) |
| return; |
| |
| // test whether the launch defines the JUnit attributes |
| String portStr= launch.getAttribute(JUnitLaunchConfigurationConstants.ATTR_PORT); |
| if (portStr == null) |
| return; |
| try { |
| final int port= Integer.parseInt(portStr); |
| fTrackedLaunches.remove(launch); |
| connectTestRunner(launch, javaProject, port); |
| } catch (NumberFormatException e) { |
| return; |
| } |
| } |
| |
| private void connectTestRunner(ILaunch launch, IJavaProject javaProject, int port) { |
| TestRunSession testRunSession= new TestRunSession(launch, javaProject, port); |
| addTestRunSession(testRunSession); |
| } |
| } |
| |
| /** |
| * @deprecated to prevent deprecation warnings |
| */ |
| private static final class LegacyTestRunSessionListener implements ITestRunSessionListener { |
| private TestRunSession fActiveTestRunSession; |
| private ITestSessionListener fTestSessionListener; |
| |
| public void sessionAdded(TestRunSession testRunSession) { |
| // Only serve one legacy ITestRunListener at a time, since they cannot distinguish between different concurrent test sessions: |
| if (fActiveTestRunSession != null) |
| return; |
| |
| fActiveTestRunSession= testRunSession; |
| |
| fTestSessionListener= new ITestSessionListener() { |
| public void testAdded(TestElement testElement) { |
| } |
| |
| public void sessionStarted() { |
| org.eclipse.jdt.junit.ITestRunListener[] testRunListeners= JUnitCorePlugin.getDefault().getTestRunListeners(); |
| for (int i= 0; i < testRunListeners.length; i++) { |
| testRunListeners[i].testRunStarted(fActiveTestRunSession.getTotalCount()); |
| } |
| } |
| public void sessionTerminated() { |
| org.eclipse.jdt.junit.ITestRunListener[] testRunListeners= JUnitCorePlugin.getDefault().getTestRunListeners(); |
| for (int i= 0; i < testRunListeners.length; i++) { |
| testRunListeners[i].testRunTerminated(); |
| } |
| sessionRemoved(fActiveTestRunSession); |
| } |
| public void sessionStopped(long elapsedTime) { |
| org.eclipse.jdt.junit.ITestRunListener[] testRunListeners= JUnitCorePlugin.getDefault().getTestRunListeners(); |
| for (int i= 0; i < testRunListeners.length; i++) { |
| testRunListeners[i].testRunStopped(elapsedTime); |
| } |
| sessionRemoved(fActiveTestRunSession); |
| } |
| public void sessionEnded(long elapsedTime) { |
| org.eclipse.jdt.junit.ITestRunListener[] testRunListeners= JUnitCorePlugin.getDefault().getTestRunListeners(); |
| for (int i= 0; i < testRunListeners.length; i++) { |
| testRunListeners[i].testRunEnded(elapsedTime); |
| } |
| sessionRemoved(fActiveTestRunSession); |
| } |
| public void runningBegins() { |
| // ignore |
| } |
| public void testStarted(TestCaseElement testCaseElement) { |
| org.eclipse.jdt.junit.ITestRunListener[] testRunListeners= JUnitCorePlugin.getDefault().getTestRunListeners(); |
| for (int i= 0; i < testRunListeners.length; i++) { |
| testRunListeners[i].testStarted(testCaseElement.getId(), testCaseElement.getTestName()); |
| } |
| } |
| |
| public void testFailed(TestElement testElement, Status status, String trace, String expected, String actual) { |
| org.eclipse.jdt.junit.ITestRunListener[] testRunListeners= JUnitCorePlugin.getDefault().getTestRunListeners(); |
| for (int i= 0; i < testRunListeners.length; i++) { |
| testRunListeners[i].testFailed(status.getOldCode(), testElement.getId(), testElement.getTestName(), trace); |
| } |
| } |
| |
| public void testEnded(TestCaseElement testCaseElement) { |
| org.eclipse.jdt.junit.ITestRunListener[] testRunListeners= JUnitCorePlugin.getDefault().getTestRunListeners(); |
| for (int i= 0; i < testRunListeners.length; i++) { |
| testRunListeners[i].testEnded(testCaseElement.getId(), testCaseElement.getTestName()); |
| } |
| } |
| |
| public void testReran(TestCaseElement testCaseElement, Status status, String trace, String expectedResult, String actualResult) { |
| org.eclipse.jdt.junit.ITestRunListener[] testRunListeners= JUnitCorePlugin.getDefault().getTestRunListeners(); |
| for (int i= 0; i < testRunListeners.length; i++) { |
| testRunListeners[i].testReran(testCaseElement.getId(), testCaseElement.getClassName(), testCaseElement.getTestMethodName(), status.getOldCode(), trace); |
| } |
| } |
| |
| public boolean acceptsSwapToDisk() { |
| return true; |
| } |
| }; |
| fActiveTestRunSession.addTestSessionListener(fTestSessionListener); |
| } |
| |
| public void sessionRemoved(TestRunSession testRunSession) { |
| if (fActiveTestRunSession == testRunSession) { |
| fActiveTestRunSession.removeTestSessionListener(fTestSessionListener); |
| fTestSessionListener= null; |
| fActiveTestRunSession= null; |
| } |
| } |
| } |
| |
| private final ListenerList fTestRunSessionListeners= new ListenerList(); |
| /** |
| * Active test run sessions, youngest first. |
| */ |
| private final LinkedList/*<TestRunSession>*/ fTestRunSessions= new LinkedList(); |
| private final ILaunchListener fLaunchListener= new JUnitLaunchListener(); |
| |
| /** |
| * Starts the model (called by the {@link JUnitCorePlugin} on startup). |
| */ |
| public void start() { |
| ILaunchManager launchManager= DebugPlugin.getDefault().getLaunchManager(); |
| launchManager.addLaunchListener(fLaunchListener); |
| |
| /* |
| * TODO: restore on restart: |
| * - only import headers! |
| * - only import last n sessions; remove all other files in historyDirectory |
| */ |
| // File historyDirectory= JUnitPlugin.getHistoryDirectory(); |
| // File[] swapFiles= historyDirectory.listFiles(); |
| // if (swapFiles != null) { |
| // Arrays.sort(swapFiles, new Comparator() { |
| // public int compare(Object o1, Object o2) { |
| // String name1= ((File) o1).getName(); |
| // String name2= ((File) o2).getName(); |
| // return name1.compareTo(name2); |
| // } |
| // }); |
| // for (int i= 0; i < swapFiles.length; i++) { |
| // final File file= swapFiles[i]; |
| // SafeRunner.run(new ISafeRunnable() { |
| // public void run() throws Exception { |
| // importTestRunSession(file ); |
| // } |
| // public void handleException(Throwable exception) { |
| // JUnitPlugin.log(exception); |
| // } |
| // }); |
| // } |
| // } |
| |
| addTestRunSessionListener(new LegacyTestRunSessionListener()); |
| } |
| |
| /** |
| * Stops the model (called by the {@link JUnitCorePlugin} on shutdown). |
| */ |
| public void stop() { |
| ILaunchManager launchManager= DebugPlugin.getDefault().getLaunchManager(); |
| launchManager.removeLaunchListener(fLaunchListener); |
| |
| File historyDirectory= JUnitCorePlugin.getHistoryDirectory(); |
| File[] swapFiles= historyDirectory.listFiles(); |
| if (swapFiles != null) { |
| for (int i= 0; i < swapFiles.length; i++) { |
| swapFiles[i].delete(); |
| } |
| } |
| |
| // for (Iterator iter= fTestRunSessions.iterator(); iter.hasNext();) { |
| // final TestRunSession session= (TestRunSession) iter.next(); |
| // SafeRunner.run(new ISafeRunnable() { |
| // public void run() throws Exception { |
| // session.swapOut(); |
| // } |
| // public void handleException(Throwable exception) { |
| // JUnitPlugin.log(exception); |
| // } |
| // }); |
| // } |
| } |
| |
| |
| public void addTestRunSessionListener(ITestRunSessionListener listener) { |
| fTestRunSessionListeners.add(listener); |
| } |
| |
| public void removeTestRunSessionListener(ITestRunSessionListener listener) { |
| fTestRunSessionListeners.remove(listener); |
| } |
| |
| |
| /** |
| * @return a list of active {@link TestRunSession}s. The list is a copy of |
| * the internal data structure and modifications do not affect the |
| * global list of active sessions. The list is sorted by age, youngest first. |
| */ |
| public synchronized List getTestRunSessions() { |
| return new ArrayList(fTestRunSessions); |
| } |
| |
| /** |
| * Adds the given {@link TestRunSession} and notifies all registered |
| * {@link ITestRunSessionListener}s. |
| * |
| * @param testRunSession the session to add |
| */ |
| public void addTestRunSession(TestRunSession testRunSession) { |
| Assert.isNotNull(testRunSession); |
| ArrayList toRemove= new ArrayList(); |
| |
| synchronized (this) { |
| Assert.isLegal(! fTestRunSessions.contains(testRunSession)); |
| fTestRunSessions.addFirst(testRunSession); |
| |
| int maxCount = Platform.getPreferencesService().getInt(JUnitCorePlugin.CORE_PLUGIN_ID, JUnitPreferencesConstants.MAX_TEST_RUNS, 10, null); |
| int size= fTestRunSessions.size(); |
| if (size > maxCount) { |
| List excess= fTestRunSessions.subList(maxCount, size); |
| for (Iterator iter= excess.iterator(); iter.hasNext();) { |
| TestRunSession oldSession= (TestRunSession) iter.next(); |
| if (!(oldSession.isStarting() || oldSession.isRunning() || oldSession.isKeptAlive())) { |
| toRemove.add(oldSession); |
| iter.remove(); |
| } |
| } |
| } |
| } |
| |
| for (int i= 0; i < toRemove.size(); i++) { |
| TestRunSession oldSession= (TestRunSession) toRemove.get(i); |
| notifyTestRunSessionRemoved(oldSession); |
| } |
| notifyTestRunSessionAdded(testRunSession); |
| } |
| |
| /** |
| * Imports a test run session from the given file. |
| * |
| * @param file a file containing a test run session transcript |
| * @return the imported test run session |
| * @throws CoreException if the import failed |
| */ |
| public static TestRunSession importTestRunSession(File file) throws CoreException { |
| try { |
| SAXParserFactory parserFactory= SAXParserFactory.newInstance(); |
| // parserFactory.setValidating(true); // TODO: add DTD and debug flag |
| SAXParser parser= parserFactory.newSAXParser(); |
| TestRunHandler handler= new TestRunHandler(); |
| parser.parse(file, handler); |
| TestRunSession session= handler.getTestRunSession(); |
| JUnitCorePlugin.getModel().addTestRunSession(session); |
| return session; |
| } catch (ParserConfigurationException e) { |
| throwImportError(file, e); |
| } catch (SAXException e) { |
| throwImportError(file, e); |
| } catch (IOException e) { |
| throwImportError(file, e); |
| } |
| return null; // does not happen |
| } |
| |
| public static void importIntoTestRunSession(File swapFile, TestRunSession testRunSession) throws CoreException { |
| try { |
| SAXParserFactory parserFactory= SAXParserFactory.newInstance(); |
| // parserFactory.setValidating(true); // TODO: add DTD and debug flag |
| SAXParser parser= parserFactory.newSAXParser(); |
| TestRunHandler handler= new TestRunHandler(testRunSession); |
| parser.parse(swapFile, handler); |
| } catch (ParserConfigurationException e) { |
| throwImportError(swapFile, e); |
| } catch (SAXException e) { |
| throwImportError(swapFile, e); |
| } catch (IOException e) { |
| throwImportError(swapFile, e); |
| } |
| } |
| |
| /** |
| * Exports the given test run session. |
| * |
| * @param testRunSession the test run session |
| * @param file the destination |
| * @throws CoreException if an error occurred |
| */ |
| public static void exportTestRunSession(TestRunSession testRunSession, File file) throws CoreException { |
| FileOutputStream out= null; |
| try { |
| out= new FileOutputStream(file); |
| exportTestRunSession(testRunSession, out); |
| |
| } catch (IOException e) { |
| throwExportError(file, e); |
| } catch (TransformerConfigurationException e) { |
| throwExportError(file, e); |
| } catch (TransformerException e) { |
| throwExportError(file, e); |
| } finally { |
| if (out != null) { |
| try { |
| out.close(); |
| } catch (IOException e2) { |
| JUnitCorePlugin.log(e2); |
| } |
| } |
| } |
| } |
| |
| public static void exportTestRunSession(TestRunSession testRunSession, OutputStream out) |
| throws TransformerFactoryConfigurationError, TransformerException { |
| |
| Transformer transformer= TransformerFactory.newInstance().newTransformer(); |
| InputSource inputSource= new InputSource(); |
| SAXSource source= new SAXSource(new TestRunSessionSerializer(testRunSession), inputSource); |
| StreamResult result= new StreamResult(out); |
| transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8"); //$NON-NLS-1$ |
| transformer.setOutputProperty(OutputKeys.INDENT, "yes"); //$NON-NLS-1$ |
| /* |
| * Bug in Xalan: Only indents if proprietary property |
| * org.apache.xalan.templates.OutputProperties.S_KEY_INDENT_AMOUNT is set. |
| * |
| * Bug in Xalan as shipped with J2SE 5.0: |
| * Does not read the indent-amount property at all >:-(. |
| */ |
| try { |
| transformer.setOutputProperty("{http://xml.apache.org/xalan}indent-amount", "2"); //$NON-NLS-1$ //$NON-NLS-2$ |
| } catch (IllegalArgumentException e) { |
| // no indentation today... |
| } |
| transformer.transform(source, result); |
| } |
| |
| private static void throwExportError(File file, Exception e) throws CoreException { |
| throw new CoreException(new org.eclipse.core.runtime.Status(IStatus.ERROR, |
| JUnitCorePlugin.getPluginId(), |
| Messages.format(ModelMessages.JUnitModel_could_not_write, BasicElementLabels.getPathLabel(file)), |
| e)); |
| } |
| |
| private static void throwImportError(File file, Exception e) throws CoreException { |
| throw new CoreException(new org.eclipse.core.runtime.Status(IStatus.ERROR, |
| JUnitCorePlugin.getPluginId(), |
| Messages.format(ModelMessages.JUnitModel_could_not_read, BasicElementLabels.getPathLabel(file)), |
| e)); |
| } |
| |
| /** |
| * Removes the given {@link TestRunSession} and notifies all registered |
| * {@link ITestRunSessionListener}s. |
| * |
| * @param testRunSession the session to remove |
| */ |
| public void removeTestRunSession(TestRunSession testRunSession) { |
| boolean existed; |
| synchronized (this) { |
| existed= fTestRunSessions.remove(testRunSession); |
| } |
| if (existed) { |
| notifyTestRunSessionRemoved(testRunSession); |
| } |
| testRunSession.removeSwapFile(); |
| } |
| |
| private void notifyTestRunSessionRemoved(TestRunSession testRunSession) { |
| testRunSession.stopTestRun(); |
| ILaunch launch= testRunSession.getLaunch(); |
| if (launch != null) { |
| ILaunchManager launchManager= DebugPlugin.getDefault().getLaunchManager(); |
| launchManager.removeLaunch(launch); |
| } |
| |
| Object[] listeners = fTestRunSessionListeners.getListeners(); |
| for (int i = 0; i < listeners.length; ++i) { |
| ((ITestRunSessionListener) listeners[i]).sessionRemoved(testRunSession); |
| } |
| } |
| |
| private void notifyTestRunSessionAdded(TestRunSession testRunSession) { |
| Object[] listeners = fTestRunSessionListeners.getListeners(); |
| for (int i = 0; i < listeners.length; ++i) { |
| ((ITestRunSessionListener) listeners[i]).sessionAdded(testRunSession); |
| } |
| } |
| |
| } |