Skip to content

Commit 09aeb55

Browse files
authored
Add method to map from Description to Target (#104)
* Add method to map from Description to Target * Fix handling of suite-level failures: * AtomicTest: Add support for suite descriptions * EachTestNotifierInit: Remove 'ensureAtomicTestOf(Description)'; optimized runner acquisition * LifecycleHooks: 'describeChild' - Ensure runner can support specified child * RunAnnouncer: Add private shims to create ephemeral atomic test objects for ignored tests and suite failures
1 parent 49c2adb commit 09aeb55

File tree

6 files changed

+143
-42
lines changed

6 files changed

+143
-42
lines changed

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,18 @@ public class AddFailure {
2626
public static void intercept(@This final EachTestNotifier notifier, @SuperCall final Callable<?> proxy,
2727
@Argument(0) final Throwable targetException) throws Exception {
2828

29+
// if this isn't a multi-failure wrapper exception
2930
if ( ! (targetException instanceof MultipleFailureException)) {
31+
// get atomic test for this notifier ('null' for suite notifiers)
3032
AtomicTest atomicTest = EachTestNotifierInit.getAtomicTestOf(notifier);
33+
// if atomic test exists
3134
if (atomicTest != null) {
35+
// set test exception ("throwable")
3236
atomicTest.setThrowable(targetException);
3337
}
3438
}
3539

40+
// invoke intercepted method
3641
LifecycleHooks.callProxy(proxy);
3742
}
3843
}

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

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ public AtomicTest(Description description) {
3838
this.runner = Run.getThreadRunner();
3939
this.description = description;
4040
this.particles = getParticles(runner, description);
41-
this.identity = this.particles.get(0);
41+
this.identity = (particles.isEmpty()) ? null : particles.get(0);
4242
}
4343

4444
/**
@@ -62,7 +62,7 @@ public Description getDescription() {
6262
/**
6363
* Get the "identity" method for this atomic test - the core {@link Test &#64;Test} method.
6464
*
65-
* @return core method associated with this atomic test
65+
* @return core method associated with this atomic test (may be {@code null})
6666
*/
6767
public FrameworkMethod getIdentity() {
6868
return identity;
@@ -71,7 +71,7 @@ public FrameworkMethod getIdentity() {
7171
/**
7272
* Get the "particle" methods of which this atomic test is composed.
7373
*
74-
* @return list of methods that compose this atomic test
74+
* @return list of methods that compose this atomic test (may be empty)
7575
*/
7676
public List<FrameworkMethod> getParticles() {
7777
return particles;
@@ -128,6 +128,15 @@ public boolean isTheory() {
128128
}
129129
}
130130

131+
/**
132+
* Determine if this atomic test represents a test method.
133+
*
134+
* @return {@code true} if this atomic test represents a test method; otherwise {@code false}
135+
*/
136+
public boolean isTest() {
137+
return description.isTest();
138+
}
139+
131140
/**
132141
* {@inheritDoc}
133142
*/
@@ -161,7 +170,7 @@ public int hashCode() {
161170
*
162171
* @param runner JUnit test runner
163172
* @param description JUnit method description
164-
* @return list of "particle" methods
173+
* @return list of "particle" methods (may be empty)
165174
*/
166175
private List<FrameworkMethod> getParticles(Object runner, Description description) {
167176
List<FrameworkMethod> particles = new ArrayList<>();

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

Lines changed: 41 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -35,35 +35,54 @@ public class EachTestNotifierInit {
3535
public static void interceptor(@Argument(0) final RunNotifier notifier,
3636
@Argument(1) final Description description) {
3737

38+
// if notifier for test
3839
if (description.isTest()) {
39-
newAtomicTestFor(description);
40-
Object runner = Run.getThreadRunner();
40+
// create new atomic test object
41+
AtomicTest atomicTest = newAtomicTestFor(description);
42+
// get current thread runner
43+
Object runner = atomicTest.getRunner();
44+
4145
FrameworkMethod method = null;
46+
// get particle methods of current runner
4247
List<Object> children = LifecycleHooks.invoke(runner, "getChildren");
48+
// iterate particle methods
4349
for (Object child : children) {
50+
// if this method matches subject description
4451
if (description.equals(LifecycleHooks.describeChild(runner, child))) {
52+
// subject method resolved
4553
method = (FrameworkMethod) child;
4654
break;
4755
}
4856
}
57+
// if method unknown
4958
if (method == null) {
5059
try {
60+
// get method => description mappings
5161
Map<FrameworkMethod, Description> assoc =
5262
LifecycleHooks.getFieldValue(runner, "methodDescriptions");
63+
// iterate method => description mappings
5364
for (Entry<FrameworkMethod, Description> entry : assoc.entrySet()) {
65+
// if this entry maps subject description
5466
if (description.equals(entry.getValue())) {
67+
// subject method resolved
5568
method = entry.getKey();
5669
}
5770
}
5871
} catch (IllegalAccessException | NoSuchFieldException | SecurityException e) {
5972
// nothing to do here
6073
}
6174
}
75+
// if method resolved
6276
if (method != null) {
77+
// get target test class instance
6378
Object target = CreateTest.getTargetFor(runner, method);
79+
// if target exists
6480
if (target != null) {
81+
// create description <=> target mappings
6582
createMappingsFor(description.hashCode(), target);
83+
// otherwise (target not yet created)
6684
} else {
85+
// store [runner + method] => description mapping for 'setTestTarget' method
6786
HASHCODE_TO_DESCRIPTION.put(Objects.hash(runner, method), description.hashCode());
6887
}
6988
} else {
@@ -76,31 +95,17 @@ public static void interceptor(@Argument(0) final RunNotifier notifier,
7695
* Create new atomic test object for the specified description.
7796
*
7897
* @param description description of the test that is about to be run
79-
* @return {@link AtomicTest} object (may be {@code null})
98+
* @return {@link AtomicTest} object
8099
*/
81-
static AtomicTest newAtomicTestFor(Description description) {
82-
AtomicTest atomicTest = null;
83-
if (description.isTest()) {
84-
atomicTest = new AtomicTest(description);
85-
DESCRIPTION_TO_ATOMICTEST.put(description.hashCode(), atomicTest);
86-
}
100+
private static AtomicTest newAtomicTestFor(Description description) {
101+
// create new atomic test object
102+
AtomicTest atomicTest = new AtomicTest(description);
103+
// create description => atomic test mapping
104+
DESCRIPTION_TO_ATOMICTEST.put(description.hashCode(), atomicTest);
105+
87106
return atomicTest;
88107
}
89108

90-
/**
91-
* Get the atomic test object for the specified method description; create if absent.
92-
*
93-
* @param description JUnit method description
94-
* @return {@link AtomicTest} object (may be {@code null})
95-
*/
96-
static AtomicTest ensureAtomicTestOf(Description description) {
97-
if (DESCRIPTION_TO_ATOMICTEST.containsKey(description.hashCode())) {
98-
return DESCRIPTION_TO_ATOMICTEST.get(description.hashCode());
99-
} else {
100-
return newAtomicTestFor(description);
101-
}
102-
}
103-
104109
/**
105110
* Get the atomic test object for the specified method description.
106111
*
@@ -114,6 +119,16 @@ static AtomicTest getAtomicTestOf(Description description) {
114119
return null;
115120
}
116121

122+
/**
123+
* Get the test class instance for the specified method description.
124+
*
125+
* @param description JUnit method description
126+
* @return test class instance (may be {@code null})
127+
*/
128+
static Object getTargetOf(Description description) {
129+
return DESCRIPTION_TO_TARGET.get(description.hashCode());
130+
}
131+
117132
/**
118133
* Get the atomic test for the specified notifier.
119134
*
@@ -135,8 +150,11 @@ static AtomicTest getAtomicTestOf(EachTestNotifier notifier) {
135150
* @return {@code true} is mapping was established; otherwise {@code false}
136151
*/
137152
static boolean setTestTarget(Object runner, FrameworkMethod method, Object target) {
153+
// get [runner + method] => description mapping
138154
Integer descriptionHash = getDescriptionHashFor(runner, method);
155+
// if mapping exists
139156
if (descriptionHash != null) {
157+
// create description <=> target mappings
140158
createMappingsFor(descriptionHash, target);
141159
return true;
142160
}

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

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -348,15 +348,49 @@ public static AtomicTest getAtomicTestOf(Description description) {
348348
return EachTestNotifierInit.getAtomicTestOf(description);
349349
}
350350

351+
/**
352+
* Get the test class instance for the specified method description.
353+
*
354+
* @param description JUnit method description
355+
* @return test class instance (may be {@code null})
356+
*/
357+
public static Object getTargetOf(Description description) {
358+
return EachTestNotifierInit.getTargetOf(description);
359+
}
360+
351361
/**
352362
* Get the description for the specified child object.
353363
*
354364
* @param runner target {@link org.junit.runners.ParentRunner ParentRunner} object
355365
* @param child child object
356-
* @return {@link Description} for the specified framework method
366+
* @return {@link Description} for the specified framework method (may be {@code null})
357367
*/
358368
public static Description describeChild(Object runner, Object child) {
359-
return invoke(runner, "describeChild", child);
369+
if (runner != null && child != null) {
370+
Class<?> runnerType = getSupportedType(runner);
371+
if (runnerType != null && runnerType.isInstance(child)) {
372+
return invoke(runner, "describeChild", child);
373+
}
374+
}
375+
return null;
376+
}
377+
378+
/**
379+
* Get the type of children supported by the specified runner.
380+
*
381+
* @param runner target {@link org.junit.runners.ParentRunner ParentRunner} object
382+
* @return supported child type; {@code null} if undetermined
383+
*/
384+
private static Class<?> getSupportedType(Object runner) {
385+
for (Method method : runner.getClass().getDeclaredMethods()) {
386+
if ("describeChild".equals(method.getName())) {
387+
Class<?>[] paramTypes = method.getParameterTypes();
388+
if ((paramTypes.length == 1) && (paramTypes[0] != Object.class)) {
389+
return paramTypes[0];
390+
}
391+
}
392+
}
393+
return null;
360394
}
361395

362396
/**

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

Lines changed: 44 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ public class RunAnnouncer extends RunListener implements JUnitWatcher {
2626
@Override
2727
public void testStarted(Description description) throws Exception {
2828
LOGGER.debug("testStarted: {}", description);
29-
AtomicTest atomicTest = EachTestNotifierInit.getAtomicTestOf(description);
29+
AtomicTest atomicTest = ensureAtomicTestOf(description);
3030
for (RunWatcher watcher : LifecycleHooks.getRunWatchers()) {
3131
watcher.testStarted(atomicTest);
3232
}
@@ -38,7 +38,7 @@ public void testStarted(Description description) throws Exception {
3838
@Override
3939
public void testFinished(Description description) throws Exception {
4040
LOGGER.debug("testFinished: {}", description);
41-
AtomicTest atomicTest = EachTestNotifierInit.getAtomicTestOf(description);
41+
AtomicTest atomicTest = ensureAtomicTestOf(description);
4242
for (RunWatcher watcher : LifecycleHooks.getRunWatchers()) {
4343
watcher.testFinished(atomicTest);
4444
}
@@ -50,7 +50,7 @@ public void testFinished(Description description) throws Exception {
5050
@Override
5151
public void testFailure(Failure failure) throws Exception {
5252
LOGGER.debug("testFailure: {}", failure);
53-
AtomicTest atomicTest = EachTestNotifierInit.getAtomicTestOf(failure.getDescription());
53+
AtomicTest atomicTest = ensureAtomicTestOf(failure);
5454
for (RunWatcher watcher : LifecycleHooks.getRunWatchers()) {
5555
watcher.testFailure(atomicTest, failure.getException());
5656
}
@@ -62,7 +62,7 @@ public void testFailure(Failure failure) throws Exception {
6262
@Override
6363
public void testAssumptionFailure(Failure failure) {
6464
LOGGER.debug("testAssumptionFailure: {}", failure);
65-
AtomicTest atomicTest = EachTestNotifierInit.getAtomicTestOf(failure.getDescription());
65+
AtomicTest atomicTest = ensureAtomicTestOf(failure);
6666
for (RunWatcher watcher : LifecycleHooks.getRunWatchers()) {
6767
watcher.testAssumptionFailure(atomicTest, (AssumptionViolatedException) failure.getException());
6868
}
@@ -74,13 +74,49 @@ public void testAssumptionFailure(Failure failure) {
7474
@Override
7575
public void testIgnored(Description description) throws Exception {
7676
LOGGER.debug("testIgnored: {}", description);
77-
AtomicTest atomicTest = EachTestNotifierInit.ensureAtomicTestOf(description);
77+
AtomicTest atomicTest = ensureAtomicTestOf(description);
7878
for (RunWatcher watcher : LifecycleHooks.getRunWatchers()) {
7979
watcher.testIgnored(atomicTest);
8080
}
81-
// if this isn't a retried test
82-
if ( ! RetriedTest.isRetriedTest(description)) {
83-
EachTestNotifierInit.releaseAtomicTestOf(description);
81+
}
82+
83+
/**
84+
* Get the atomic test object for the specified method description.
85+
* <p>
86+
* <b>NOTE</b>: For ignored tests, this method returns an ephemeral object.
87+
*
88+
* @param description JUnit method description
89+
* @return {@link AtomicTest} object
90+
*/
91+
private static AtomicTest ensureAtomicTestOf(Description description) {
92+
// get atomic test for this description
93+
AtomicTest atomicTest = EachTestNotifierInit.getAtomicTestOf(description);
94+
// if none was found
95+
if (atomicTest == null) {
96+
// create ephemeral atomic test object
97+
atomicTest = new AtomicTest(description);
98+
}
99+
return atomicTest;
100+
}
101+
102+
/**
103+
* Get the atomic test object for the specified failure.
104+
* <p>
105+
* <b>NOTE</b>: For suite failures, this method returns an ephemeral object.
106+
*
107+
* @param failure
108+
* @return {@link AtomicTest} object
109+
*/
110+
private static AtomicTest ensureAtomicTestOf(Failure failure) {
111+
// get atomic test for this description
112+
AtomicTest atomicTest = EachTestNotifierInit.getAtomicTestOf(failure.getDescription());
113+
// if none was found
114+
if (atomicTest == null) {
115+
// create ephemeral atomic test object
116+
atomicTest = new AtomicTest(failure.getDescription());
117+
// set the exception for this atomic test
118+
atomicTest.setThrowable(failure.getException());
84119
}
120+
return atomicTest;
85121
}
86122
}

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

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
import java.util.concurrent.ConcurrentHashMap;
66
import java.util.concurrent.ConcurrentMap;
77

8-
import org.junit.Test;
98
import org.junit.internal.runners.model.ReflectiveCallable;
109
import org.junit.runner.Description;
1110
import org.junit.runners.model.FrameworkMethod;
@@ -175,12 +174,12 @@ private static boolean fireBeforeInvocation(Object runner, Object child, Reflect
175174
Description description = LifecycleHooks.describeChild(runner, child);
176175
if (LOGGER.isDebugEnabled()) {
177176
try {
178-
LOGGER.debug("beforeInvocation: {}", description);
177+
LOGGER.debug("beforeInvocation: {}", (description != null) ? description : child);
179178
} catch (Throwable t) {
180179
// nothing to do here
181180
}
182181
}
183-
if (null != ((FrameworkMethod) child).getAnnotation(Test.class)) {
182+
if ((description != null) && description.isTest()) {
184183
DESCRIPTION_TO_CALLABLE.put(description.hashCode(), callable);
185184
}
186185
}
@@ -213,10 +212,10 @@ private static boolean fireAfterInvocation(Object runner, Object child, Reflecti
213212
if (0 == depthGauge.decreaseDepth()) {
214213
METHOD_DEPTH.get().remove(callable.hashCode());
215214
if (child instanceof FrameworkMethod) {
216-
Description description = LifecycleHooks.describeChild(runner, child);
217215
if (LOGGER.isDebugEnabled()) {
218216
try {
219-
LOGGER.debug("afterInvocation: {}", description);
217+
Description description = LifecycleHooks.describeChild(runner, child);
218+
LOGGER.debug("afterInvocation: {}", (description != null) ? description : child);
220219
} catch (Throwable t) {
221220
// nothing to do here
222221
}

0 commit comments

Comments
 (0)