Skip to content

Commit 1d99c54

Browse files
author
Thomas Wolf
committed
Bug 372209 - Capture screenshots before @after clean-ups run
SWTBotJUnit4Runner takes screenshots after @after methods have run, which frequently leads to not very useful screenshots. Amend this by taking the screenshot as soon as possible after a test method failure, before @after runs. The former mechanism based on a RunListener still exists and will take a screenshot (if none had been taken earlier for a particular test method) if some @After/@before method fails or a rule such as @ExpectedException fails the test. In those cases, the behavior is unchanged: these screenshots are still taken after @after has run. Change-Id: Ifda1e76a5ddd3d29ee0826056b87b88cef31a290 Signed-off-by: Thomas Wolf <thomas.wolf@paranor.ch>
1 parent f7f2854 commit 1d99c54

File tree

5 files changed

+282
-23
lines changed

5 files changed

+282
-23
lines changed

org.eclipse.swtbot.junit4_x/META-INF/MANIFEST.MF

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ Import-Package: org.apache.log4j;version="1.2.12",
1010
org.eclipse.swt.widgets,
1111
org.eclipse.swtbot.swt.finder,
1212
org.eclipse.swtbot.swt.finder.utils,
13+
org.junit;version="4.8.0",
14+
org.junit.runner;version="4.8.0",
1315
org.junit.runner.notification;version="4.8.0",
1416
org.junit.runners;version="4.8.0",
1517
org.junit.runners.model;version="4.8.0"

org.eclipse.swtbot.junit4_x/src/org/eclipse/swtbot/swt/finder/junit/SWTBotJUnit4Suite.java

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
*******************************************************************************/
1111
package org.eclipse.swtbot.swt.finder.junit;
1212

13-
import org.junit.runner.notification.RunListener;
13+
import org.eclipse.swtbot.swt.finder.junit.internal.ScreenshotCaptureNotifier;
1414
import org.junit.runner.notification.RunNotifier;
1515
import org.junit.runners.Suite;
1616
import org.junit.runners.model.RunnerBuilder;
@@ -50,13 +50,11 @@ public SWTBotJUnit4Suite(Class<?> klass, Class<?>[] annotatedClasses) throws Exc
5050
* @see org.junit.runners.Suite#run(RunNotifier)
5151
*/
5252
public void run(RunNotifier notifier) {
53-
RunListener failureSpy = new ScreenshotCaptureListener();
54-
notifier.removeListener(failureSpy); // remove existing listeners that could be added by suite or class runners
55-
notifier.addListener(failureSpy);
56-
try {
53+
if (notifier instanceof ScreenshotCaptureNotifier) {
5754
super.run(notifier);
58-
} finally {
59-
notifier.removeListener(failureSpy);
55+
} else {
56+
RunNotifier wrappedNotifier = new ScreenshotCaptureNotifier(notifier);
57+
super.run(wrappedNotifier);
6058
}
6159
}
6260

org.eclipse.swtbot.junit4_x/src/org/eclipse/swtbot/swt/finder/junit/SWTBotJunit4ClassRunner.java

Lines changed: 112 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,24 @@
88
* Contributors:
99
* Hans Schwaebli - initial API and implementation (Bug 259787)
1010
* Toby Weston - initial API and implementation (Bug 259787)
11+
* Thomas Wolf - Bug 372209.
1112
*******************************************************************************/
1213
package org.eclipse.swtbot.swt.finder.junit;
1314

14-
import org.junit.runner.notification.RunListener;
15+
import java.lang.reflect.Method;
16+
17+
import org.eclipse.swtbot.swt.finder.junit.internal.ScreenshotCaptureNotifier;
18+
import org.junit.Test;
19+
import org.junit.Test.None;
20+
import org.junit.runner.Description;
21+
import org.junit.runner.notification.Failure;
1522
import org.junit.runner.notification.RunNotifier;
1623
import org.junit.runners.BlockJUnit4ClassRunner;
24+
import org.junit.runners.model.FrameworkMethod;
1725

1826
/**
19-
* A runner that captures screenshots on test failures. If you wish to launch your application for your tests use
27+
* A runner that captures screenshots on test failures. If you wish to launch
28+
* your application for your tests use
2029
* {@link SWTBotApplicationLauncherClassRunner}. Typical usage is:
2130
*
2231
* <pre>
@@ -40,20 +49,114 @@ public class SWTBotJunit4ClassRunner extends BlockJUnit4ClassRunner {
4049
/**
4150
* Creates a SWTBotRunner to run {@code klass}
4251
*
43-
* @throws Exception if the test class is malformed.
52+
* @throws Exception
53+
* if the test class is malformed.
4454
*/
4555
public SWTBotJunit4ClassRunner(Class<?> klass) throws Exception {
4656
super(klass);
4757
}
4858

59+
@Override
4960
public void run(RunNotifier notifier) {
50-
RunListener failureSpy = new ScreenshotCaptureListener();
51-
notifier.removeListener(failureSpy); // remove existing listeners that could be added by suite or class runners
52-
notifier.addListener(failureSpy);
53-
try {
61+
if (notifier instanceof ScreenshotCaptureNotifier) {
5462
super.run(notifier);
55-
} finally {
56-
notifier.removeListener(failureSpy);
63+
} else {
64+
RunNotifier wrappedNotifier = new ScreenshotCaptureNotifier(notifier);
65+
super.run(wrappedNotifier);
66+
}
67+
}
68+
69+
@Override
70+
protected void runChild(final FrameworkMethod method, RunNotifier notifier) {
71+
Description description = describeChild(method);
72+
if (isIgnored(method)) {
73+
notifier.fireTestIgnored(description);
74+
} else {
75+
FrameworkMethod toRun = method;
76+
if (notifier instanceof ScreenshotCaptureNotifier) {
77+
toRun = new CapturingFrameworkMethod(method.getMethod(), description,
78+
(ScreenshotCaptureNotifier) notifier);
79+
}
80+
runLeaf(methodBlock(toRun), description, notifier);
81+
}
82+
}
83+
84+
/**
85+
* A {@link FrameworkMethod} that tries to take a screenshot via the given
86+
* {@link ScreenshotCaptureNotifier} as soon as the test method fails,
87+
* before any @After clean-ups run.
88+
*/
89+
private static class CapturingFrameworkMethod extends FrameworkMethod {
90+
91+
// We test for this exception using the name because it only became
92+
// public recently in JUnit 4.12
93+
// TODO: remove once we require JUnit 4.12 or greater
94+
private static final String ASSUMPTION_VIOLATED_EXCEPTION_NAME = "AssumptionViolatedException";
95+
96+
private final Class<? extends Throwable> expectedException;
97+
private final Description description;
98+
private final ScreenshotCaptureNotifier notifier;
99+
100+
public CapturingFrameworkMethod(Method method, Description description, ScreenshotCaptureNotifier notifier) {
101+
super(method);
102+
this.description = description;
103+
this.notifier = notifier;
104+
// Determine expected exception, if any
105+
Test annotation = method.getAnnotation(Test.class);
106+
expectedException = getExpectedException(annotation);
107+
}
108+
109+
@Override
110+
public Object invokeExplosively(Object target, Object... params) throws Throwable {
111+
Object result = null;
112+
try {
113+
result = super.invokeExplosively(target, params);
114+
} catch (Throwable e) {
115+
// A timeout will give us an InterruptedException here and make
116+
// us capture a screenshot, too.
117+
// TODO: test for org.junit.AssumptionViolatedException once
118+
// we require JUnit 4.12 or greater
119+
if (!ASSUMPTION_VIOLATED_EXCEPTION_NAME.equals(e.getClass().getSimpleName())) {
120+
if (expectedException == null || !expectedException.isAssignableFrom(e.getClass())) {
121+
// Unexpected exception
122+
notifier.captureScreenshot(new Failure(description, e));
123+
}
124+
}
125+
throw e;
126+
}
127+
if (expectedException != null) {
128+
// No exception, but we expected one
129+
notifier.captureScreenshot(new Failure(description, null));
130+
// No need to raise an exception, an outer statement will do so.
131+
}
132+
// If the test didn't throw an exception but there is an outer
133+
// @ExpectedException rule that would expect one and then fail the
134+
// test, ScreenshotCaptureNotifier.fireTestFailed() will eventually
135+
// be called and will create the screenshot. In that case, however,
136+
// @After clean-ups already have run.
137+
return result;
138+
}
139+
140+
private Class<? extends Throwable> getExpectedException(Test annotation) {
141+
if (annotation == null || annotation.expected() == None.class) {
142+
return null;
143+
} else {
144+
return annotation.expected();
145+
}
146+
}
147+
148+
@Override
149+
public boolean equals(Object obj) {
150+
// The new fields added in this sub-class shall not influence
151+
// equality.
152+
return super.equals(obj);
153+
}
154+
155+
@Override
156+
public int hashCode() {
157+
// The new fields added in this sub-class shall not influence the
158+
// hash code.
159+
return super.hashCode();
57160
}
58161
}
59162

org.eclipse.swtbot.junit4_x/src/org/eclipse/swtbot/swt/finder/junit/ScreenshotCaptureListener.java

Lines changed: 38 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,19 @@
44
* are made available under the terms of the Eclipse Public License v1.0
55
* which accompanies this distribution, and is available at
66
* http://www.eclipse.org/legal/epl-v10.html
7-
*
7+
*
88
* Contributors:
99
* Hans Schwaebli - initial API and implementation (Bug 259787)
10+
* Thomas Wolf - Bug 372209
1011
*******************************************************************************/
1112
package org.eclipse.swtbot.swt.finder.junit;
1213

14+
import java.io.File;
15+
1316
import org.apache.log4j.Logger;
1417
import org.eclipse.swtbot.swt.finder.utils.SWTBotPreferences;
1518
import org.eclipse.swtbot.swt.finder.utils.SWTUtils;
19+
import org.junit.runner.Description;
1620
import org.junit.runner.notification.Failure;
1721
import org.junit.runner.notification.RunListener;
1822

@@ -25,38 +29,65 @@
2529
*/
2630
public final class ScreenshotCaptureListener extends RunListener {
2731
/** The logger. */
28-
private static Logger log = Logger.getLogger(SWTBotApplicationLauncherClassRunner.class);
32+
private static Logger log = Logger.getLogger(SWTBotApplicationLauncherClassRunner.class);
2933

3034
/** Counts the screenshots to determine if maximum number is reached. */
31-
private static int screenshotCounter = 0;
35+
private static int screenshotCounter = 0;
3236

37+
@Override
3338
public void testFailure(Failure failure) throws Exception {
3439
captureScreenshot(failure);
3540
}
3641

42+
/**
43+
* Removes a previously taken screenshot, if any.
44+
*
45+
* @param description
46+
* of the test
47+
* @since 2.4
48+
*/
49+
public void removeScreenshot(Description description) {
50+
File file = new File(getFileName(new Failure(description, null)));
51+
if (file.exists()) {
52+
log.debug("Removing screenshot '" + file.getPath() + '\''); //$NON-NLS-1$
53+
if (!file.delete() && file.exists()) {
54+
log.warn("Could not remove screenshot " + file.getAbsolutePath()); //$NON-NLS-1$
55+
}
56+
--screenshotCounter;
57+
}
58+
}
59+
3760
private void captureScreenshot(Failure failure) {
3861
try {
3962
int maximumScreenshots = SWTBotPreferences.MAX_ERROR_SCREENSHOT_COUNT;
40-
String fileName = SWTBotPreferences.SCREENSHOTS_DIR + "/" + failure.getTestHeader() + "." + SWTBotPreferences.SCREENSHOT_FORMAT.toLowerCase(); //$NON-NLS-1$
63+
String fileName = getFileName(failure);
4164
if (++screenshotCounter <= maximumScreenshots) {
4265
captureScreenshot(fileName);
4366
} else {
44-
log.info("No screenshot captured for '" + failure.getTestHeader() + "' because maximum number of screenshots reached: " //$NON-NLS-1$
67+
log.info("No screenshot captured for '" + failure.getTestHeader() //$NON-NLS-1$
68+
+ "' because maximum number of screenshots reached: "
4569
+ maximumScreenshots);
4670
}
4771
} catch (Exception e) {
4872
log.warn("Could not capture screenshot", e); //$NON-NLS-1$
4973
}
5074
}
5175

76+
private String getFileName(Failure failure) {
77+
return SWTBotPreferences.SCREENSHOTS_DIR + "/" + failure.getTestHeader() + "." //$NON-NLS-1$
78+
+ SWTBotPreferences.SCREENSHOT_FORMAT.toLowerCase();
79+
}
80+
5281
private boolean captureScreenshot(String fileName) {
5382
return SWTUtils.captureScreenshot(fileName);
5483
}
55-
84+
85+
@Override
5686
public int hashCode() {
5787
return 31;
5888
}
5989

90+
@Override
6091
public boolean equals(Object obj) {
6192
if (this == obj)
6293
return true;
@@ -66,5 +97,5 @@ public boolean equals(Object obj) {
6697
return false;
6798
return true;
6899
}
69-
100+
70101
}

0 commit comments

Comments
 (0)