1 /*******************************************************************************
2  * Copyright (c) 2000, 2018 IBM Corporation and others.
3  *
4  * This program and the accompanying materials
5  * are made available under the terms of the Eclipse Public License 2.0
6  * which accompanies this distribution, and is available at
7  * https://www.eclipse.org/legal/epl-2.0/
8  *
9  * SPDX-License-Identifier: EPL-2.0
10  *
11  * Contributors:
12  *     IBM Corporation - initial API and implementation
13  *     Achim Demelt <a.demelt@exxcellent.de> - [junit] Separate UI from non-UI code - https://bugs.eclipse.org/bugs/show_bug.cgi?id=278844
14  *     Thirumala Reddy Mutchukota <thirumala@google.com> - [JUnit] Avoid rerun test launch on UI thread - https://bugs.eclipse.org/bugs/show_bug.cgi?id=411841
15  *******************************************************************************/
16 package org.eclipse.jdt.internal.junit.model;
17 
18 import java.io.File;
19 import java.text.DateFormat;
20 import java.text.SimpleDateFormat;
21 import java.util.ArrayList;
22 import java.util.Arrays;
23 import java.util.Date;
24 import java.util.HashMap;
25 import java.util.List;
26 
27 import org.eclipse.jdt.junit.model.ITestElement;
28 import org.eclipse.jdt.junit.model.ITestElementContainer;
29 import org.eclipse.jdt.junit.model.ITestRunSession;
30 
31 import org.eclipse.core.runtime.Assert;
32 import org.eclipse.core.runtime.CoreException;
33 import org.eclipse.core.runtime.ListenerList;
34 
35 import org.eclipse.debug.core.DebugPlugin;
36 import org.eclipse.debug.core.ILaunch;
37 import org.eclipse.debug.core.ILaunchConfiguration;
38 import org.eclipse.debug.core.ILaunchManager;
39 import org.eclipse.debug.core.ILaunchesListener2;
40 
41 import org.eclipse.jdt.core.IJavaProject;
42 
43 import org.eclipse.jdt.internal.junit.JUnitCorePlugin;
44 import org.eclipse.jdt.internal.junit.JUnitMessages;
45 import org.eclipse.jdt.internal.junit.launcher.ITestKind;
46 import org.eclipse.jdt.internal.junit.launcher.JUnitLaunchConfigurationConstants;
47 import org.eclipse.jdt.internal.junit.model.TestElement.Status;
48 import org.eclipse.jdt.internal.junit.runner.MessageIds;
49 
50 
51 /**
52  * A test run session holds all information about a test run, i.e.
53  * launch configuration, launch, test tree (including results).
54  */
55 public class TestRunSession implements ITestRunSession {
56 
57 	/**
58 	 * The launch, or <code>null</code> iff this session was run externally.
59 	 */
60 	private final ILaunch fLaunch;
61 	private final String fTestRunName;
62 	/**
63 	 * Java project, or <code>null</code>.
64 	 */
65 	private final IJavaProject fProject;
66 
67 	private final ITestKind fTestRunnerKind;
68 
69 	/**
70 	 * Test runner client or <code>null</code>.
71 	 */
72 	private RemoteTestRunnerClient fTestRunnerClient;
73 
74 	private final ListenerList<ITestSessionListener> fSessionListeners;
75 
76 	/**
77 	 * The model root, or <code>null</code> if swapped to disk.
78 	 */
79 	private TestRoot fTestRoot;
80 
81 	/**
82 	 * The test run session's cached result, or <code>null</code> if <code>fTestRoot != null</code>.
83 	 */
84 	private Result fTestResult;
85 
86 	/**
87 	 * Map from testId to testElement.
88 	 */
89 	private HashMap<String, TestElement> fIdToTest;
90 
91 	/**
92 	 * The TestSuites for which additional children are expected.
93 	 */
94 	private List<IncompleteTestSuite> fIncompleteTestSuites;
95 
96 	private List<IncompleteTestSuite> fFactoryTestSuites;
97 
98 	/**
99 	 * Suite for unrooted test case elements, or <code>null</code>.
100 	 */
101 	private TestSuiteElement fUnrootedSuite;
102 
103 	private static final String EMPTY_STRING= ""; //$NON-NLS-1$
104 
105 	/**
106 	 * Tags included in this test run.
107 	 */
108 	private String fIncludeTags;
109 
110 	/**
111 	 * Tags excluded from this test run.
112 	 */
113 	private String fExcludeTags;
114 
115 	/**
116  	 * Number of tests started during this test run.
117  	 */
118 	volatile int fStartedCount;
119 	/**
120 	 * Number of tests ignored during this test run.
121 	 */
122 	volatile int fIgnoredCount;
123 	/**
124 	 * Number of tests whose assumption failed during this test run.
125 	 */
126 	volatile int fAssumptionFailureCount;
127 	/**
128 	 * Number of errors during this test run.
129 	 */
130 	volatile int fErrorCount;
131 	/**
132 	 * Number of failures during this test run.
133 	 */
134 	volatile int fFailureCount;
135 	/**
136 	 * Total number of tests to run.
137 	 */
138 	volatile int fTotalCount;
139 	/**
140 	 * <ul>
141 	 * <li>If &gt; 0: Start time in millis</li>
142 	 * <li>If &lt; 0: Unique identifier for imported test run</li>
143 	 * <li>If = 0: Session not started yet</li>
144 	 * </ul>
145 	 */
146 	volatile long fStartTime;
147 	volatile boolean fIsRunning;
148 
149 	volatile boolean fIsStopped;
150 
151 
152 	/**
153 	 * Creates a test run session.
154 	 *
155 	 * @param testRunName name of the test run
156 	 * @param project may be <code>null</code>
157 	 */
TestRunSession(String testRunName, IJavaProject project)158 	public TestRunSession(String testRunName, IJavaProject project) {
159 		//TODO: check assumptions about non-null fields
160 
161 		fLaunch= null;
162 		fProject= project;
163 		fStartTime= -System.currentTimeMillis();
164 
165 		Assert.isNotNull(testRunName);
166 		fTestRunName= testRunName;
167 		fTestRunnerKind= ITestKind.NULL; //TODO
168 
169 		fTestRoot= new TestRoot(this);
170 		fIdToTest= new HashMap<>();
171 
172 		fTestRunnerClient= null;
173 
174 		fSessionListeners= new ListenerList<>();
175 	}
176 
177 
TestRunSession(ILaunch launch, IJavaProject project, int port)178 	public TestRunSession(ILaunch launch, IJavaProject project, int port) {
179 		Assert.isNotNull(launch);
180 
181 		fLaunch= launch;
182 		fProject= project;
183 
184 		ILaunchConfiguration launchConfiguration= launch.getLaunchConfiguration();
185 		if (launchConfiguration != null) {
186 			fTestRunName= launchConfiguration.getName();
187 			fTestRunnerKind= JUnitLaunchConfigurationConstants.getTestRunnerKind(launchConfiguration);
188 		} else {
189 			fTestRunName= project.getElementName();
190 			fTestRunnerKind= ITestKind.NULL;
191 		}
192 
193 		fTestRoot= new TestRoot(this);
194 		fIdToTest= new HashMap<>();
195 
196 		fTestRunnerClient= new RemoteTestRunnerClient();
197 		fTestRunnerClient.startListening(new ITestRunListener2[] { new TestSessionNotifier() }, port);
198 
199 		final ILaunchManager launchManager= DebugPlugin.getDefault().getLaunchManager();
200 		launchManager.addLaunchListener(new ILaunchesListener2() {
201 			@Override
202 			public void launchesTerminated(ILaunch[] launches) {
203 				if (Arrays.asList(launches).contains(fLaunch)) {
204 					if (fTestRunnerClient != null) {
205 						fTestRunnerClient.stopWaiting();
206 					}
207 					launchManager.removeLaunchListener(this);
208 				}
209 			}
210 			@Override
211 			public void launchesRemoved(ILaunch[] launches) {
212 				if (Arrays.asList(launches).contains(fLaunch)) {
213 					if (fTestRunnerClient != null) {
214 						fTestRunnerClient.stopWaiting();
215 					}
216 					launchManager.removeLaunchListener(this);
217 				}
218 			}
219 			@Override
220 			public void launchesChanged(ILaunch[] launches) {
221 			}
222 			@Override
223 			public void launchesAdded(ILaunch[] launches) {
224 			}
225 		});
226 
227 		fSessionListeners= new ListenerList<>();
228 		addTestSessionListener(new TestRunListenerAdapter(this));
229 	}
230 
reset()231 	void reset() {
232 		fStartedCount= 0;
233 		fFailureCount= 0;
234 		fAssumptionFailureCount = 0;
235 		fErrorCount= 0;
236 		fIgnoredCount= 0;
237 		fTotalCount= 0;
238 
239 		fTestRoot= new TestRoot(this);
240 		fTestResult= null;
241 		fIdToTest= new HashMap<>();
242 	}
243 
244 	@Override
getProgressState()245 	public ProgressState getProgressState() {
246 		if (isRunning()) {
247 			return ProgressState.RUNNING;
248 		}
249 		if (isStopped()) {
250 			return ProgressState.STOPPED;
251 		}
252 		return ProgressState.COMPLETED;
253 	}
254 
255 	@Override
getTestResult(boolean includeChildren)256 	public Result getTestResult(boolean includeChildren) {
257 		if (fTestRoot != null) {
258 			return fTestRoot.getTestResult(true);
259 		} else {
260 			return fTestResult;
261 		}
262 	}
263 
264 	@Override
getChildren()265 	public ITestElement[] getChildren() {
266 		return getTestRoot().getChildren();
267 	}
268 
269 	@Override
getFailureTrace()270 	public FailureTrace getFailureTrace() {
271 		return null;
272 	}
273 
274 	@Override
getParentContainer()275 	public ITestElementContainer getParentContainer() {
276 		return null;
277 	}
278 
279 	@Override
getTestRunSession()280 	public ITestRunSession getTestRunSession() {
281 		return this;
282 	}
283 
284 
getTestRoot()285 	public synchronized TestRoot getTestRoot() {
286 		swapIn(); //TODO: TestRoot should stay (e.g. for getTestRoot().getStatus())
287 		return fTestRoot;
288 	}
289 
290 	/*
291 	 * @see org.eclipse.jdt.junit.model.ITestRunSession#getJavaProject()
292 	 */
293 	@Override
getLaunchedProject()294 	public IJavaProject getLaunchedProject() {
295 		return fProject;
296 	}
297 
getTestRunnerKind()298 	public ITestKind getTestRunnerKind() {
299 		return fTestRunnerKind;
300 	}
301 
302 
303 	/**
304 	 * @return the launch, or <code>null</code> iff this session was run externally
305 	 */
getLaunch()306 	public ILaunch getLaunch() {
307 		return fLaunch;
308 	}
309 
310 	@Override
getTestRunName()311 	public String getTestRunName() {
312 		return fTestRunName;
313 	}
314 
getErrorCount()315 	public int getErrorCount() {
316 		return fErrorCount;
317 	}
318 
getFailureCount()319 	public int getFailureCount() {
320 		return fFailureCount;
321 	}
322 
getAssumptionFailureCount()323 	public int getAssumptionFailureCount() {
324 		return fAssumptionFailureCount;
325 	}
326 
getStartedCount()327 	public int getStartedCount() {
328 		return fStartedCount;
329 	}
330 
getIgnoredCount()331 	public int getIgnoredCount() {
332 		return fIgnoredCount;
333 	}
334 
getTotalCount()335 	public int getTotalCount() {
336 		return fTotalCount;
337 	}
338 
getStartTime()339 	public long getStartTime() {
340 		return fStartTime;
341 	}
342 
343 	/**
344 	 * @return <code>true</code> iff the session has been stopped or terminated
345 	 */
isStopped()346 	public boolean isStopped() {
347 		return fIsStopped;
348 	}
349 
addTestSessionListener(ITestSessionListener listener)350 	public synchronized void addTestSessionListener(ITestSessionListener listener) {
351 		swapIn();
352 		fSessionListeners.add(listener);
353 	}
354 
removeTestSessionListener(ITestSessionListener listener)355 	public void removeTestSessionListener(ITestSessionListener listener) {
356 		fSessionListeners.remove(listener);
357 	}
358 
swapOut()359 	public synchronized void swapOut() {
360 		if (fTestRoot == null)
361 			return;
362 		if (isRunning() || isStarting() || isKeptAlive())
363 			return;
364 
365 		for (ITestSessionListener registered : fSessionListeners) {
366 			if (! registered.acceptsSwapToDisk())
367 				return;
368 		}
369 
370 		try {
371 			File swapFile= getSwapFile();
372 
373 			JUnitModel.exportTestRunSession(this, swapFile);
374 			fTestResult= fTestRoot.getTestResult(true);
375 			fTestRoot= null;
376 			fTestRunnerClient= null;
377 			fIdToTest= new HashMap<>();
378 			fIncompleteTestSuites= null;
379 			fFactoryTestSuites= null;
380 			fUnrootedSuite= null;
381 
382 		} catch (IllegalStateException e) {
383 			JUnitCorePlugin.log(e);
384 		} catch (CoreException e) {
385 			JUnitCorePlugin.log(e);
386 		}
387 	}
388 
isStarting()389 	public boolean isStarting() {
390 		return getStartTime() == 0 && fLaunch != null && ! fLaunch.isTerminated();
391 	}
392 
393 
removeSwapFile()394 	public void removeSwapFile() {
395 		File swapFile= getSwapFile();
396 		if (swapFile.exists())
397 			swapFile.delete();
398 	}
399 
getSwapFile()400 	private File getSwapFile() throws IllegalStateException {
401 		File historyDir= JUnitCorePlugin.getHistoryDirectory();
402 		String isoTime= new SimpleDateFormat("yyyyMMdd-HHmmss.SSS").format(new Date(getStartTime())); //$NON-NLS-1$
403 		String swapFileName= isoTime + ".xml"; //$NON-NLS-1$
404 		return new File(historyDir, swapFileName);
405 	}
406 
407 
swapIn()408 	public synchronized void swapIn() {
409 		if (fTestRoot != null)
410 			return;
411 
412 		try {
413 			JUnitModel.importIntoTestRunSession(getSwapFile(), this);
414 		} catch (IllegalStateException e) {
415 			JUnitCorePlugin.log(e);
416 			fTestRoot= new TestRoot(this);
417 			fTestResult= null;
418 		} catch (CoreException e) {
419 			JUnitCorePlugin.log(e);
420 			fTestRoot= new TestRoot(this);
421 			fTestResult= null;
422 		}
423 	}
424 
stopTestRun()425 	public void stopTestRun() {
426 		if (isRunning() || ! isKeptAlive())
427 			fIsStopped= true;
428 		if (fTestRunnerClient != null)
429 			fTestRunnerClient.stopTest();
430 	}
431 
432 	/**
433 	 * @return <code>true</code> iff the runtime VM of this test session is still alive
434 	 */
isKeptAlive()435 	public boolean isKeptAlive() {
436 		if (fTestRunnerClient != null
437 				&& fLaunch != null
438 				&& fTestRunnerClient.isRunning()
439 				&& ILaunchManager.DEBUG_MODE.equals(fLaunch.getLaunchMode())) {
440 			ILaunchConfiguration config= fLaunch.getLaunchConfiguration();
441 			try {
442 				return config != null
443 				&& config.getAttribute(JUnitLaunchConfigurationConstants.ATTR_KEEPRUNNING, false);
444 			} catch (CoreException e) {
445 				return false;
446 			}
447 
448 		} else {
449 			return false;
450 		}
451 	}
452 
453 	/**
454 	 * @return <code>true</code> iff this session has been started, but not ended nor stopped nor terminated
455 	 */
isRunning()456 	public boolean isRunning() {
457 		return fIsRunning;
458 	}
459 
460 	/**
461 	 * Reruns the given test method if the session is kept alive.
462 	 *
463 	 * @param testId test id
464 	 * @param className test class name
465 	 * @param testName test method name
466 	 * @return <code>false</code> iff the rerun could not be started
467 	 */
rerunTest(String testId, String className, String testName)468 	public boolean rerunTest(String testId, String className, String testName) {
469 		if (isKeptAlive()) {
470 			Status status= ((TestCaseElement) getTestElement(testId)).getStatus();
471 			if (status == Status.ERROR) {
472 				fErrorCount--;
473 			} else if (status == Status.FAILURE) {
474 				fFailureCount--;
475 			}
476 			fTestRunnerClient.rerunTest(testId, className, testName);
477 			return true;
478 		}
479 		return false;
480 	}
481 
getTestElement(String id)482 	public TestElement getTestElement(String id) {
483 		return fIdToTest.get(id);
484 	}
485 
addTreeEntry(String treeEntry)486 	private TestElement addTreeEntry(String treeEntry) {
487 		// format: testId","testName","isSuite","testcount","isDynamicTest","parentId","displayName","parameterTypes","uniqueId
488 		int index0= treeEntry.indexOf(',');
489 		String id= treeEntry.substring(0, index0);
490 
491 		StringBuffer testNameBuffer= new StringBuffer(100);
492 		int index1= scanTestName(treeEntry, index0 + 1, testNameBuffer);
493 		String testName= testNameBuffer.toString().trim();
494 
495 		int index2= treeEntry.indexOf(',', index1 + 1);
496 		boolean isSuite= treeEntry.substring(index1 + 1, index2).equals("true"); //$NON-NLS-1$
497 
498 		int testCount;
499 		boolean isDynamicTest;
500 		String parentId;
501 		String displayName;
502 		StringBuffer displayNameBuffer= new StringBuffer(100);
503 		String[] parameterTypes;
504 		StringBuffer parameterTypesBuffer= new StringBuffer(200);
505 		String uniqueId;
506 		StringBuffer uniqueIdBuffer= new StringBuffer(200);
507 		int index3= treeEntry.indexOf(',', index2 + 1);
508 		if (index3 == -1) {
509 			testCount= Integer.parseInt(treeEntry.substring(index2 + 1));
510 			isDynamicTest= false;
511 			parentId= null;
512 			displayName= null;
513 			parameterTypes= null;
514 			uniqueId= null;
515 		} else {
516 			testCount= Integer.parseInt(treeEntry.substring(index2 + 1, index3));
517 
518 			int index4= treeEntry.indexOf(',', index3 + 1);
519 			isDynamicTest= treeEntry.substring(index3 + 1, index4).equals("true"); //$NON-NLS-1$
520 
521 			int index5= treeEntry.indexOf(',', index4 + 1);
522 			parentId= treeEntry.substring(index4 + 1, index5);
523 			if (parentId.equals("-1")) { //$NON-NLS-1$
524 				parentId= null;
525 			}
526 
527 			int index6= scanTestName(treeEntry, index5 + 1, displayNameBuffer);
528 			displayName= displayNameBuffer.toString().trim();
529 			if (displayName.equals(testName)) {
530 				displayName= null;
531 			}
532 
533 			int index7= scanTestName(treeEntry, index6 + 1, parameterTypesBuffer);
534 			String parameterTypesString= parameterTypesBuffer.toString().trim();
535 			if (parameterTypesString.isEmpty()) {
536 				parameterTypes= null;
537 			} else {
538 				parameterTypes= parameterTypesString.split(","); //$NON-NLS-1$
539 				Arrays.parallelSetAll(parameterTypes, i -> parameterTypes[i].trim());
540 			}
541 
542 			scanTestName(treeEntry, index7 + 1, uniqueIdBuffer);
543 			uniqueId= uniqueIdBuffer.toString().trim();
544 			if (uniqueId.isEmpty()) {
545 				uniqueId= null;
546 			}
547 		}
548 
549 		if (isDynamicTest) {
550 			if (parentId != null) {
551 				for (IncompleteTestSuite suite : fFactoryTestSuites) {
552 					if (parentId.equals(suite.fTestSuiteElement.getId())) {
553 						return createTestElement(suite.fTestSuiteElement, id, testName, isSuite, testCount, isDynamicTest, displayName, parameterTypes, uniqueId);
554 					}
555 				}
556 			}
557 			return createTestElement(getUnrootedSuite(), id, testName, isSuite, testCount, isDynamicTest, displayName, parameterTypes, uniqueId); // should not reach here
558 		} else {
559 			if (fIncompleteTestSuites.isEmpty()) {
560 				return createTestElement(fTestRoot, id, testName, isSuite, testCount, isDynamicTest, displayName, parameterTypes, uniqueId);
561 			} else {
562 				int suiteIndex= fIncompleteTestSuites.size() - 1;
563 				IncompleteTestSuite openSuite= fIncompleteTestSuites.get(suiteIndex);
564 				openSuite.fOutstandingChildren--;
565 				if (openSuite.fOutstandingChildren <= 0)
566 					fIncompleteTestSuites.remove(suiteIndex);
567 				return createTestElement(openSuite.fTestSuiteElement, id, testName, isSuite, testCount, isDynamicTest, displayName, parameterTypes, uniqueId);
568 			}
569 		}
570 	}
571 
createTestElement(TestSuiteElement parent, String id, String testName, boolean isSuite, int testCount, boolean isDynamicTest, String displayName, String[] parameterTypes, String uniqueId)572 	public TestElement createTestElement(TestSuiteElement parent, String id, String testName, boolean isSuite, int testCount, boolean isDynamicTest, String displayName, String[] parameterTypes, String uniqueId) {
573 		TestElement testElement;
574 		if (parameterTypes != null && parameterTypes.length > 1) {
575 			parameterTypes= Arrays.stream(parameterTypes).map(t -> t.trim()).toArray(String[]::new);
576 		}
577 		if (isSuite) {
578 			TestSuiteElement testSuiteElement= new TestSuiteElement(parent, id, testName, testCount, displayName, parameterTypes, uniqueId);
579 			testElement= testSuiteElement;
580 			if (testCount > 0) {
581 				fIncompleteTestSuites.add(new IncompleteTestSuite(testSuiteElement, testCount));
582 			} else if (fFactoryTestSuites != null) {
583 				fFactoryTestSuites.add(new IncompleteTestSuite(testSuiteElement, testCount));
584 			}
585 		} else {
586 			testElement= new TestCaseElement(parent, id, testName, displayName, isDynamicTest, parameterTypes, uniqueId);
587 		}
588 		fIdToTest.put(id, testElement);
589 		return testElement;
590 	}
591 
592 	/**
593 	 * Append the test name from <code>s</code> to <code>testName</code>.
594 	 *
595 	 * @param s the string to scan
596 	 * @param start the offset of the first character in <code>s</code>
597 	 * @param testName the result
598 	 *
599 	 * @return the index of the next ','
600 	 */
scanTestName(String s, int start, StringBuffer testName)601 	private int scanTestName(String s, int start, StringBuffer testName) {
602 		boolean inQuote= false;
603 		int i= start;
604 		for (; i < s.length(); i++) {
605 			char c= s.charAt(i);
606 			if (c == '\\' && !inQuote) {
607 				inQuote= true;
608 				continue;
609 			} else if (inQuote) {
610 				inQuote= false;
611 				testName.append(c);
612 			} else if (c == ',')
613 				break;
614 			else
615 				testName.append(c);
616 		}
617 		return i;
618 	}
619 
getUnrootedSuite()620 	private TestSuiteElement getUnrootedSuite() {
621 		if (fUnrootedSuite == null) {
622 			fUnrootedSuite= (TestSuiteElement) createTestElement(fTestRoot, "-2", JUnitMessages.TestRunSession_unrootedTests, true, 0, false, JUnitMessages.TestRunSession_unrootedTests, null, null); //$NON-NLS-1$
623 		}
624 		return fUnrootedSuite;
625 	}
626 
627 	/**
628 	 * An {@link ITestRunListener2} that listens to events from the
629 	 * {@link RemoteTestRunnerClient} and translates them into high-level model
630 	 * events (broadcasted to {@link ITestSessionListener}s).
631 	 */
632 	private class TestSessionNotifier implements ITestRunListener2 {
633 
634 		@Override
testRunStarted(int testCount)635 		public void testRunStarted(int testCount) {
636 			fIncompleteTestSuites= new ArrayList<>();
637 			fFactoryTestSuites= new ArrayList<>();
638 
639 			fStartedCount= 0;
640 			fIgnoredCount= 0;
641 			fFailureCount= 0;
642 			fAssumptionFailureCount = 0;
643 			fErrorCount= 0;
644 			fTotalCount= testCount;
645 
646 			fStartTime= System.currentTimeMillis();
647 			fIsRunning= true;
648 
649 			for (ITestSessionListener listener : fSessionListeners) {
650 				listener.sessionStarted();
651 			}
652 		}
653 
654 		@Override
testRunEnded(long elapsedTime)655 		public void testRunEnded(long elapsedTime) {
656 			fIsRunning= false;
657 
658 			for (ITestSessionListener listener : fSessionListeners) {
659 				listener.sessionEnded(elapsedTime);
660 			}
661 		}
662 
663 		@Override
testRunStopped(long elapsedTime)664 		public void testRunStopped(long elapsedTime) {
665 			fIsRunning= false;
666 			fIsStopped= true;
667 
668 			for (ITestSessionListener listener : fSessionListeners) {
669 				listener.sessionStopped(elapsedTime);
670 			}
671 		}
672 
673 		@Override
testRunTerminated()674 		public void testRunTerminated() {
675 			fIsRunning= false;
676 			fIsStopped= true;
677 
678 			for (ITestSessionListener listener : fSessionListeners) {
679 				listener.sessionTerminated();
680 			}
681 		}
682 
683 		@Override
testTreeEntry(String description)684 		public void testTreeEntry(String description) {
685 			TestElement testElement= addTreeEntry(description);
686 
687 			for (ITestSessionListener listener : fSessionListeners) {
688 				listener.testAdded(testElement);
689 			}
690 		}
691 
createUnrootedTestElement(String testId, String testName)692 		private TestElement createUnrootedTestElement(String testId, String testName) {
693 			TestSuiteElement unrootedSuite= getUnrootedSuite();
694 			TestElement testElement= createTestElement(unrootedSuite, testId, testName, false, 1, false, testName, null, null);
695 
696 			for (ITestSessionListener listener : fSessionListeners) {
697 				listener.testAdded(testElement);
698 			}
699 
700 			return testElement;
701 		}
702 
703 		@Override
testStarted(String testId, String testName)704 		public void testStarted(String testId, String testName) {
705 			if (fStartedCount == 0) {
706 				for (ITestSessionListener listener : fSessionListeners) {
707 					listener.runningBegins();
708 				}
709 			}
710 			TestElement testElement= getTestElement(testId);
711 			if (testElement == null) {
712 				testElement= createUnrootedTestElement(testId, testName);
713 			} else if (! (testElement instanceof TestCaseElement)) {
714 				logUnexpectedTest(testId, testElement);
715 				return;
716 			}
717 			TestCaseElement testCaseElement= (TestCaseElement) testElement;
718 			setStatus(testCaseElement, Status.RUNNING);
719 
720 			if (testCaseElement.isDynamicTest()) {
721 				fTotalCount++;
722 			}
723 
724 			fStartedCount++;
725 
726 			for (ITestSessionListener listener : fSessionListeners) {
727 				listener.testStarted(testCaseElement);
728 			}
729 		}
730 
731 		@Override
testEnded(String testId, String testName)732 		public void testEnded(String testId, String testName) {
733 			boolean isIgnored= testName.startsWith(MessageIds.IGNORED_TEST_PREFIX);
734 
735 			TestElement testElement= getTestElement(testId);
736 			if (testElement == null) {
737 				testElement= createUnrootedTestElement(testId, testName);
738 			} else if (! (testElement instanceof TestCaseElement)) {
739 				if (isIgnored) {
740 					testElement.setAssumptionFailed(true);
741 					fAssumptionFailureCount++;
742 					setStatus(testElement, Status.OK);
743 				} else {
744 					logUnexpectedTest(testId, testElement);
745 				}
746 				return;
747 			}
748 			TestCaseElement testCaseElement= (TestCaseElement) testElement;
749 			if (isIgnored) {
750 				testCaseElement.setIgnored(true);
751 				fIgnoredCount++;
752 			}
753 
754 			if (testCaseElement.getStatus() == Status.RUNNING)
755 				setStatus(testCaseElement, Status.OK);
756 
757 			for (ITestSessionListener listener : fSessionListeners) {
758 				listener.testEnded(testCaseElement);
759 			}
760 		}
761 
762 
763 		@Override
testFailed(int statusCode, String testId, String testName, String trace, String expected, String actual)764 		public void testFailed(int statusCode, String testId, String testName, String trace, String expected, String actual) {
765 			TestElement testElement= getTestElement(testId);
766 			if (testElement == null) {
767 				testElement= createUnrootedTestElement(testId, testName);
768 			}
769 
770 			Status status;
771 			if (testName.startsWith(MessageIds.ASSUMPTION_FAILED_TEST_PREFIX)) {
772 				testElement.setAssumptionFailed(true);
773 				fAssumptionFailureCount++;
774 				status = Status.OK;
775 			} else {
776 				status= Status.convert(statusCode);
777 			}
778 
779 			registerTestFailureStatus(testElement, status, trace, expected, actual);
780 
781 			for (ITestSessionListener listener : fSessionListeners) {
782 				listener.testFailed(testElement, status, trace, expected, actual);
783 			}
784 		}
785 
786 		@Override
testReran(String testId, String className, String testName, int statusCode, String trace, String expectedResult, String actualResult)787 		public void testReran(String testId, String className, String testName, int statusCode, String trace, String expectedResult, String actualResult) {
788 			TestElement testElement= getTestElement(testId);
789 			if (testElement == null) {
790 				testElement= createUnrootedTestElement(testId, testName);
791 			} else if (! (testElement instanceof TestCaseElement)) {
792 				logUnexpectedTest(testId, testElement);
793 				return;
794 			}
795 			TestCaseElement testCaseElement= (TestCaseElement) testElement;
796 
797 			Status status= Status.convert(statusCode);
798 			registerTestFailureStatus(testElement, status, trace, expectedResult, actualResult);
799 
800 			for (ITestSessionListener listener : fSessionListeners) {
801 				//TODO: post old & new status?
802 				listener.testReran(testCaseElement, status, trace, expectedResult, actualResult);
803 			}
804 		}
805 
logUnexpectedTest(String testId, TestElement testElement)806 		private void logUnexpectedTest(String testId, TestElement testElement) {
807 			JUnitCorePlugin.log(new Exception("Unexpected TestElement type for testId '" + testId + "': " + testElement)); //$NON-NLS-1$ //$NON-NLS-2$
808 		}
809 	}
810 
811 	private static class IncompleteTestSuite {
812 		public TestSuiteElement fTestSuiteElement;
813 		public int fOutstandingChildren;
814 
IncompleteTestSuite(TestSuiteElement testSuiteElement, int outstandingChildren)815 		public IncompleteTestSuite(TestSuiteElement testSuiteElement, int outstandingChildren) {
816 			fTestSuiteElement= testSuiteElement;
817 			fOutstandingChildren= outstandingChildren;
818 		}
819 	}
820 
registerTestFailureStatus(TestElement testElement, Status status, String trace, String expected, String actual)821 	public void registerTestFailureStatus(TestElement testElement, Status status, String trace, String expected, String actual) {
822 		testElement.setStatus(status, trace, expected, actual);
823 		if (!testElement.isAssumptionFailure()) {
824 			if (status.isError()) {
825 				fErrorCount++;
826 			} else if (status.isFailure()) {
827 				fFailureCount++;
828 			}
829 		}
830 	}
831 
registerTestEnded(TestElement testElement, boolean completed)832 	public void registerTestEnded(TestElement testElement, boolean completed) {
833 		if (testElement instanceof TestCaseElement) {
834 			fTotalCount++;
835 			if (! completed) {
836 				return;
837 			}
838 			fStartedCount++;
839 			if (((TestCaseElement) testElement).isIgnored()) {
840 				fIgnoredCount++;
841 			}
842 			if (! testElement.getStatus().isErrorOrFailure())
843 				setStatus(testElement, Status.OK);
844 		}
845 
846 		if (testElement.isAssumptionFailure()) {
847 			fAssumptionFailureCount++;
848 		}
849 	}
850 
setStatus(TestElement testElement, Status status)851 	private void setStatus(TestElement testElement, Status status) {
852 		testElement.setStatus(status);
853 	}
854 
getAllFailedTestElements()855 	public TestElement[] getAllFailedTestElements() {
856 		ArrayList<ITestElement> failures= new ArrayList<>();
857 		addFailures(failures, getTestRoot());
858 		return failures.toArray(new TestElement[failures.size()]);
859 	}
860 
addFailures(ArrayList<ITestElement> failures, ITestElement testElement)861 	private void addFailures(ArrayList<ITestElement> failures, ITestElement testElement) {
862 		Result testResult= testElement.getTestResult(true);
863 		if (testResult == Result.ERROR || testResult == Result.FAILURE) {
864 			failures.add(testElement);
865 		}
866 		if (testElement instanceof TestSuiteElement) {
867 			TestSuiteElement testSuiteElement= (TestSuiteElement) testElement;
868 			ITestElement[] children= testSuiteElement.getChildren();
869 			for (ITestElement child : children) {
870 				addFailures(failures, child);
871 			}
872 		}
873 	}
874 
875 	@Override
getElapsedTimeInSeconds()876 	public double getElapsedTimeInSeconds() {
877 		if (fTestRoot == null)
878 			return Double.NaN;
879 
880 		return fTestRoot.getElapsedTimeInSeconds();
881 	}
882 
getIncludeTags()883 	public String getIncludeTags() {
884 		if (fLaunch != null) {
885 			try {
886 				ILaunchConfiguration launchConfig= fLaunch.getLaunchConfiguration();
887 				if (launchConfig != null) {
888 					boolean hasIncludeTags= launchConfig.getAttribute(JUnitLaunchConfigurationConstants.ATTR_TEST_HAS_INCLUDE_TAGS, false);
889 					if (hasIncludeTags) {
890 						return launchConfig.getAttribute(JUnitLaunchConfigurationConstants.ATTR_TEST_INCLUDE_TAGS, EMPTY_STRING);
891 					}
892 				}
893 			} catch (CoreException e) {
894 				//ignore
895 			}
896 			return EMPTY_STRING;
897 		}
898 		return fIncludeTags;
899 	}
900 
getExcludeTags()901 	public String getExcludeTags() {
902 		if (fLaunch != null) {
903 			try {
904 				ILaunchConfiguration launchConfig= fLaunch.getLaunchConfiguration();
905 				if (launchConfig != null) {
906 					boolean hasExcludeTags= launchConfig.getAttribute(JUnitLaunchConfigurationConstants.ATTR_TEST_HAS_EXCLUDE_TAGS, false);
907 					if (hasExcludeTags) {
908 						return launchConfig.getAttribute(JUnitLaunchConfigurationConstants.ATTR_TEST_EXCLUDE_TAGS, EMPTY_STRING);
909 					}
910 				}
911 			} catch (CoreException e) {
912 				//ignore
913 			}
914 			return EMPTY_STRING;
915 		}
916 		return fExcludeTags;
917 	}
918 
setIncludeTags(String includeTags)919 	public void setIncludeTags(String includeTags) {
920 		fIncludeTags= includeTags;
921 	}
922 
923 
setExcludeTags(String excludeTags)924 	public void setExcludeTags(String excludeTags) {
925 		fExcludeTags= excludeTags;
926 	}
927 
928 	@Override
toString()929 	public String toString() {
930 		return fTestRunName + " " + DateFormat.getDateTimeInstance().format(new Date(fStartTime)); //$NON-NLS-1$
931 	}
932 }
933