Skip to content

Commit 068b16a

Browse files
authored
Merge pull request #29 from Nordstrom/pr/fix-bogus-assumptions
Turns out framework methods and test classes aren't unique contexts
2 parents 2e80e6a + 23610a7 commit 068b16a

File tree

13 files changed

+129
-154
lines changed

13 files changed

+129
-154
lines changed

README.md

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,14 +24,12 @@ Get the parent runner that owns the specified child runner.
2424
Get the parent runner that owns the specified method.
2525
* `LifecycleHooks.getRunnerForTarget(Object target)`
2626
Get the parent runner that owns the specified instance.
27-
* `LifecycleHooks.getRunnerFor(TestClass testClass)`
28-
Get the parent runner associated with the specified test class object.
27+
* `LifecycleHooks.getTargetForRunner(Object runner)`
28+
Get the JUnit test class instance owned by the specified parent runner.
2929
* `LifecycleHooks.getTestClassWith(Object method)`
3030
Get the test class associated with the specified framework method.
3131
* `LifecycleHooks.getTestClassOf(Object runner)`
3232
Get the test class object associated with the specified parent runner.
33-
* `RunReflectiveCall.getTargetFor(FrameworkMethod method)`
34-
Get the target test class instance for the specified method.
3533
* `RunReflectiveCall.getAtomicTestFor(TestClass testClass)`
3634
Get the atomic test associated with the specified test class.
3735
* `RunReflectiveCall.getAtomicTestFor(FrameworkMethod method)`

pom.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,8 @@
182182
<groupId>org.apache.maven.plugins</groupId>
183183
<artifactId>maven-surefire-plugin</artifactId>
184184
<configuration>
185+
<parallel>classes</parallel>
186+
<threadCount>5</threadCount>
185187
<argLine>-javaagent:src/test/resources/test-agent.jar</argLine>
186188
</configuration>
187189
</plugin>

src/main/java/com/nordstrom/automation/junit/AtomicTest.java

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,12 @@
2222
@SuppressWarnings("all")
2323
public class AtomicTest {
2424
private final Object runner;
25-
private final TestClass testClass;
2625
private final FrameworkMethod identity;
2726
private final List<FrameworkMethod> particles;
2827
private Throwable thrown;
2928

30-
public AtomicTest(Object runner, TestClass testClass, FrameworkMethod testMethod) {
29+
public AtomicTest(Object runner, FrameworkMethod testMethod) {
3130
this.runner = runner;
32-
this.testClass = testClass;
3331
this.identity = testMethod;
3432
this.particles = invoke(runner, "getChildren");
3533
}
@@ -43,15 +41,6 @@ public Object getRunner() {
4341
return runner;
4442
}
4543

46-
/**
47-
* Get the test class associated with this atomic test.
48-
*
49-
* @return {@link TestClass} object associated with this atomic test
50-
*/
51-
public TestClass getTestClass() {
52-
return testClass;
53-
}
54-
5544
/**
5645
* Get the "identity" method for this atomic test - the core {@link Test &#64;Test} method.
5746
*

src/main/java/com/nordstrom/automation/junit/CreateTestClass.java

Lines changed: 2 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -24,14 +24,11 @@
2424
@SuppressWarnings("squid:S1118")
2525
public class CreateTestClass {
2626
private static final ServiceLoader<TestClassWatcher> classWatcherLoader;
27-
private static final ServiceLoader<TestClassWatcher2> classWatcher2Loader;
2827
private static final Logger LOGGER = LoggerFactory.getLogger(CreateTestClass.class);
29-
private static final Map<TestClass, Object> TESTCLASS_TO_RUNNER = new ConcurrentHashMap<>();
3028
private static final Map<Object, TestClass> METHOD_TO_TESTCLASS = new ConcurrentHashMap<>();
3129

3230
static {
3331
classWatcherLoader = ServiceLoader.load(TestClassWatcher.class);
34-
classWatcher2Loader = ServiceLoader.load(TestClassWatcher2.class);
3532
}
3633

3734
/**
@@ -46,7 +43,6 @@ public static TestClass intercept(@This final Object runner, @SuperCall final Ca
4643
throws Exception {
4744

4845
TestClass testClass = (TestClass) LifecycleHooks.callProxy(proxy);
49-
TESTCLASS_TO_RUNNER.put(testClass, runner);
5046

5147
for (Object method : testClass.getAnnotatedMethods()) {
5248
METHOD_TO_TESTCLASS.put(method, testClass);
@@ -57,11 +53,6 @@ public static TestClass intercept(@This final Object runner, @SuperCall final Ca
5753
watcher.testClassCreated(testClass, runner);
5854
}
5955
}
60-
synchronized(classWatcher2Loader) {
61-
for (TestClassWatcher2 watcher : classWatcher2Loader) {
62-
watcher.testClassCreated(testClass, runner);
63-
}
64-
}
6556

6657
attachRunnerScheduler(testClass, runner);
6758
return testClass;
@@ -99,56 +90,32 @@ public void schedule(Runnable childStatement) {
9990
if (scheduled.compareAndSet(false, true)) {
10091
synchronized(classWatcherLoader) {
10192
for (TestClassWatcher watcher : classWatcherLoader) {
102-
watcher.testClassStarted(testClass);
103-
}
104-
}
105-
synchronized(classWatcher2Loader) {
106-
for (TestClassWatcher2 watcher : classWatcher2Loader) {
10793
watcher.testClassStarted(testClass, runner);
10894
}
10995
}
11096
}
11197

112-
RunReflectiveCall.fireTestStarted(testClass, childStatement);
98+
RunReflectiveCall.fireTestStarted(childStatement);
11399

114100
if (scheduler != null) {
115101
scheduler.schedule(childStatement);
116102
} else {
117103
childStatement.run();
118104
}
119105

120-
RunReflectiveCall.fireTestFinished(testClass);
106+
RunReflectiveCall.fireTestFinished(runner);
121107
}
122108

123109
public void finished() {
124110
synchronized(classWatcherLoader) {
125111
for (TestClassWatcher watcher : classWatcherLoader) {
126-
watcher.testClassFinished(testClass);
127-
}
128-
}
129-
synchronized(classWatcher2Loader) {
130-
for (TestClassWatcher2 watcher : classWatcher2Loader) {
131112
watcher.testClassFinished(testClass, runner);
132113
}
133114
}
134115
}
135116
};
136117
}
137118

138-
/**
139-
* Get the parent runner associate with the specified test class.
140-
*
141-
* @param testClass {@link TestClass} object
142-
* @return {@code ParentRunner} object associated with the specified test class
143-
*/
144-
static Object getRunnerFor(TestClass testClass) {
145-
Object runner = TESTCLASS_TO_RUNNER.get(testClass);
146-
if (runner != null) {
147-
return runner;
148-
}
149-
throw new IllegalArgumentException("No associated runner was found for specified test class");
150-
}
151-
152119
/**
153120
* Get the test class associated with the specified framework method.
154121
*

src/main/java/com/nordstrom/automation/junit/LifecycleHooks.java

Lines changed: 49 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,12 @@
77
import java.lang.reflect.Field;
88
import java.lang.reflect.InvocationTargetException;
99
import java.lang.reflect.Method;
10+
import java.util.EmptyStackException;
1011
import java.util.List;
1112
import java.util.Map;
1213
import java.util.ServiceLoader;
1314
import java.util.Set;
15+
import java.util.Stack;
1416
import java.util.concurrent.Callable;
1517
import java.util.concurrent.ConcurrentHashMap;
1618
import java.util.concurrent.CopyOnWriteArraySet;
@@ -126,12 +128,20 @@ static synchronized JUnitConfig getConfig() {
126128
*/
127129
@SuppressWarnings("squid:S1118")
128130
public static class Run {
131+
private static final ThreadLocal<Stack<Object>> runnerStack;
129132
private static final ServiceLoader<RunListener> runListenerLoader;
130133
private static final ServiceLoader<RunnerWatcher> runnerWatcherLoader;
131134
private static final Set<RunNotifier> NOTIFIERS = new CopyOnWriteArraySet<>();
132135
private static final Map<Object, Object> CHILD_TO_PARENT = new ConcurrentHashMap<>();
133136

134137
static {
138+
runnerStack = new ThreadLocal<Stack<Object>>() {
139+
@Override
140+
protected Stack<Object> initialValue() {
141+
return new Stack<>();
142+
}
143+
};
144+
135145
runListenerLoader = ServiceLoader.load(RunListener.class);
136146
runnerWatcherLoader = ServiceLoader.load(RunnerWatcher.class);
137147
}
@@ -168,7 +178,9 @@ public static void intercept(@This final Object runner, @SuperCall final Callabl
168178
}
169179
}
170180

181+
runnerStack.get().push(runner);
171182
callProxy(proxy);
183+
runnerStack.get().pop();
172184

173185
synchronized(runnerWatcherLoader) {
174186
for (RunnerWatcher watcher : runnerWatcherLoader) {
@@ -186,6 +198,16 @@ public static void intercept(@This final Object runner, @SuperCall final Callabl
186198
static Object getParentOf(Object child) {
187199
return CHILD_TO_PARENT.get(child);
188200
}
201+
202+
/**
203+
* Get the runner that owns the active thread context.
204+
*
205+
* @return active {@code ParentRunner} object
206+
* @throws EmptyStackException if called outside the scope of an active runner
207+
*/
208+
static Object getThreadRunner() {
209+
return runnerStack.get().peek();
210+
}
189211
}
190212

191213
/**
@@ -197,6 +219,7 @@ public static class CreateTest {
197219

198220
private static final ServiceLoader<TestObjectWatcher> objectWatcherLoader;
199221
private static final Map<Object, Object> TARGET_TO_RUNNER = new ConcurrentHashMap<>();
222+
private static final Map<Object, Object> RUNNER_TO_TARGET = new ConcurrentHashMap<>();
200223

201224
static {
202225
objectWatcherLoader = ServiceLoader.load(TestObjectWatcher.class);
@@ -215,6 +238,7 @@ public static Object intercept(@This final Object runner,
215238
@SuperCall final Callable<?> proxy) throws Exception {
216239
Object testObj = callProxy(proxy);
217240
TARGET_TO_RUNNER.put(testObj, runner);
241+
RUNNER_TO_TARGET.put(runner, testObj);
218242
applyTimeout(testObj);
219243

220244
synchronized(objectWatcherLoader) {
@@ -235,6 +259,16 @@ public static Object intercept(@This final Object runner,
235259
static Object getRunnerForTarget(Object target) {
236260
return TARGET_TO_RUNNER.get(target);
237261
}
262+
263+
/**
264+
* Get the JUnit test class instance for the specified class runner.
265+
*
266+
* @param runner JUnit class runner
267+
* @return JUnit test class instance for specified runner
268+
*/
269+
static Object getTargetForRunner(Object runner) {
270+
return RUNNER_TO_TARGET.get(runner);
271+
}
238272
}
239273

240274
/**
@@ -248,13 +282,13 @@ public static Object getRunnerForTarget(Object target) {
248282
}
249283

250284
/**
251-
* Get the parent runner associated with the specified test class object.
285+
* Get the JUnit test class instance for the specified class runner.
252286
*
253-
* @param testClass {@link TestClass} object
254-
* @return {@link org.junit.runners.ParentRunner ParentRunner} that owns the specified test class object
287+
* @param runner JUnit class runner
288+
* @return JUnit test class instance for specified runner
255289
*/
256-
public static Object getRunnerFor(TestClass testClass) {
257-
return CreateTestClass.getRunnerFor(testClass);
290+
public static Object getTargetForRunner(Object runner) {
291+
return CreateTest.getTargetForRunner(runner);
258292
}
259293

260294
/**
@@ -277,6 +311,16 @@ public static Object getParentOf(Object child) {
277311
return Run.getParentOf(child);
278312
}
279313

314+
/**
315+
* Get the runner that owns the active thread context.
316+
*
317+
* @return active {@code ParentRunner} object
318+
* @throws EmptyStackException if called outside the scope of an active runner
319+
*/
320+
public static Object getThreadRunner() {
321+
return Run.getThreadRunner();
322+
}
323+
280324
/**
281325
* Get the test class object associated with the specified parent runner.
282326
*

src/main/java/com/nordstrom/automation/junit/MethodWatcher.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,18 +10,20 @@ public interface MethodWatcher {
1010
/**
1111
* Invoked before each test or configuration method is invoked
1212
*
13+
* @param runner JUnit test runner
1314
* @param target "enhanced" object upon which the method was invoked
1415
* @param method {@link FrameworkMethod} object for the invoked method
1516
* @param params method invocation parameters
1617
*/
17-
void beforeInvocation(Object target, FrameworkMethod method, Object... params);
18+
void beforeInvocation(Object runner, Object target, FrameworkMethod method, Object... params);
1819

1920
/**
2021
* Invoked after each test or configuration method is invoked
2122
*
23+
* @param runner JUnit test runner
2224
* @param target "enhanced" object upon which the method was invoked
2325
* @param method {@link FrameworkMethod} object for the invoked method
2426
* @param thrown exception thrown by method; {@code null} on normal completion
2527
*/
26-
void afterInvocation(Object target, FrameworkMethod method, Throwable thrown);
28+
void afterInvocation(Object runner, Object target, FrameworkMethod method, Throwable thrown);
2729
}

src/main/java/com/nordstrom/automation/junit/RetryHandler.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ static void runChildWithRetry(Object runner, final FrameworkMethod method, RunNo
5757
doRetry = doRetry(method, thrown, count);
5858
if (doRetry) {
5959
description = RetriedTest.proxyFor(description, thrown);
60-
RunReflectiveCall.fireTestIgnored(runner, method);
60+
RunReflectiveCall.fireTestIgnored(runner);
6161
eachNotifier.fireTestIgnored();
6262
} else {
6363
eachNotifier.addFailedAssumption(thrown);
@@ -66,7 +66,7 @@ static void runChildWithRetry(Object runner, final FrameworkMethod method, RunNo
6666
doRetry = doRetry(method, thrown, count);
6767
if (doRetry) {
6868
description = RetriedTest.proxyFor(description, thrown);
69-
RunReflectiveCall.fireTestIgnored(runner, method);
69+
RunReflectiveCall.fireTestIgnored(runner);
7070
eachNotifier.fireTestIgnored();
7171
} else {
7272
eachNotifier.addFailure(thrown);

src/main/java/com/nordstrom/automation/junit/RunChild.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ public static void intercept(@This final Object runner, @SuperCall final Callabl
3232
boolean isIgnored = (null != method.getAnnotation(Ignore.class));
3333

3434
if (isIgnored) {
35-
RunReflectiveCall.fireTestIgnored(runner, method);
35+
RunReflectiveCall.fireTestIgnored(runner);
3636
}
3737

3838
if (count == 0) {

0 commit comments

Comments
 (0)