Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for hierarchical context hierarchies #817

Merged
merged 1 commit into from
Apr 28, 2014
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 78 additions & 3 deletions src/main/java/org/junit/internal/builders/AnnotatedBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,69 @@
import org.junit.runners.model.InitializationError;
import org.junit.runners.model.RunnerBuilder;

import java.lang.reflect.Modifier;


/**
* The {@code AnnotatedBuilder} is a strategy for constructing runners for test class that have been annotated with the
* {@code @RunWith} annotation. All tests within this class will be executed using the runner that was specified within
* the annotation.
* <p>
* If a runner supports inner member classes, the member classes will inherit the runner from the enclosing class, e.g.:
* <pre>
* &#064;RunWith(MyRunner.class)
* public class MyTest {
* // some tests might go here
*
* public class MyMemberClass {
* &#064;Test
* public void thisTestRunsWith_MyRunner() {
* // some test logic
* }
*
* // some more tests might go here
* }
*
* &#064;RunWith(AnotherRunner.class)
* public class AnotherMemberClass {
* // some tests might go here
*
* public class DeepInnerClass {
* &#064;Test
* public void thisTestRunsWith_AnotherRunner() {
* // some test logic
* }
* }
*
* public class DeepInheritedClass extends SuperTest {
* &#064;Test
* public void thisTestRunsWith_SuperRunner() {
* // some test logic
* }
* }
* }
* }
*
* &#064;RunWith(SuperRunner.class)
* public class SuperTest {
* // some tests might go here
* }
* </pre>
* The key points to note here are:
* <ul>
* <li>If there is no RunWith annotation, no runner will be created.</li>
* <li>The resolve step is inside-out, e.g. the closest RunWith annotation wins</li>
* <li>RunWith annotations are inherited and work as if the class was annotated itself.</li>
* <li>The default JUnit runner does not support inner member classes,
* so this is only valid for custom runners that support inner member classes.</li>
* <li>Custom runners with support for inner classes may or may not support RunWith annotations for member
* classes. Please refer to the custom runner documentation.</li>
* </ul>
*
* @see org.junit.runners.model.RunnerBuilder
* @see org.junit.runner.RunWith
* @since 4.0
*/
public class AnnotatedBuilder extends RunnerBuilder {
private static final String CONSTRUCTOR_ERROR_FORMAT = "Custom runner class %s should have a public constructor with signature %s(Class testClass)";

Expand All @@ -16,13 +79,25 @@ public AnnotatedBuilder(RunnerBuilder suiteBuilder) {

@Override
public Runner runnerForClass(Class<?> testClass) throws Exception {
RunWith annotation = testClass.getAnnotation(RunWith.class);
if (annotation != null) {
return buildRunner(annotation.value(), testClass);
for (Class<?> currentTestClass = testClass; currentTestClass != null;
currentTestClass = getEnclosingClassForNonStaticMemberClass(currentTestClass)) {
RunWith annotation = currentTestClass.getAnnotation(RunWith.class);
if (annotation != null) {
return buildRunner(annotation.value(), testClass);
}
}

return null;
}

private Class<?> getEnclosingClassForNonStaticMemberClass(Class<?> currentTestClass) {
if (currentTestClass.isMemberClass() && !Modifier.isStatic(currentTestClass.getModifiers())) {
return currentTestClass.getEnclosingClass();
} else {
return null;
}
}

public Runner buildRunner(Class<? extends Runner> runnerClass,
Class<?> testClass) throws Exception {
try {
Expand Down
107 changes: 107 additions & 0 deletions src/test/java/org/junit/internal/builders/AnnotatedBuilderTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
package org.junit.internal.builders;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runner.Runner;
import org.junit.runner.RunnerSpy;
import org.junit.runners.model.RunnerBuilder;
import org.junit.runners.model.RunnerBuilderStub;

import static org.hamcrest.core.Is.is;
import static org.hamcrest.core.IsEqual.equalTo;
import static org.hamcrest.core.IsInstanceOf.instanceOf;
import static org.hamcrest.core.IsNull.nullValue;
import static org.junit.Assert.assertThat;

public class AnnotatedBuilderTest {
private AnnotatedBuilder builder = new AnnotatedBuilder(new RunnerBuilderStub());

@Test
public void topLevelTestClassWithoutAnnotation_isRunWithDefaultRunner() throws Exception {
Runner runner = builder.runnerForClass(Object.class);
assertThat(runner, is(nullValue()));
}

@Test
public void topLevelTestClassWithAnnotation_isRunWithAnnotatedRunner() throws Exception {
Runner runner = builder.runnerForClass(OuterClass.class);
assertThat(runner, is(instanceOf(RunnerSpy.class)));

RunnerSpy runnerSpy = (RunnerSpy) runner;
assertThat(runnerSpy.getInvokedTestClass(), is(equalTo((Class) OuterClass.class)));
}

@Test
public void memberClassInsideAnnotatedTopLevelClass_isRunWithTopLevelRunner() throws Exception {
Runner runner = builder.runnerForClass(OuterClass.InnerClassWithoutOwnRunWith.class);
assertThat(runner, is(instanceOf(RunnerSpy.class)));

RunnerSpy runnerSpy = (RunnerSpy) runner;
assertThat(runnerSpy.getInvokedTestClass(), is(equalTo((Class) OuterClass.InnerClassWithoutOwnRunWith.class)));
}

@Test
public void memberClassDeepInsideAnnotatedTopLevelClass_isRunWithTopLevelRunner() throws Exception {
Runner runner = builder.runnerForClass(OuterClass.InnerClassWithoutOwnRunWith.MostInnerClass.class);
assertThat(runner, is(instanceOf(RunnerSpy.class)));

RunnerSpy runnerSpy = (RunnerSpy) runner;
assertThat(runnerSpy.getInvokedTestClass(), is(equalTo((Class) OuterClass.InnerClassWithoutOwnRunWith.MostInnerClass.class)));
}

@Test
public void annotatedMemberClassInsideAnnotatedTopLevelClass_isRunWithOwnRunner() throws Exception {
Runner runner = builder.runnerForClass(OuterClass.InnerClassWithOwnRunWith.class);
assertThat(runner, is(instanceOf(InnerRunner.class)));

RunnerSpy runnerSpy = (RunnerSpy) runner;
assertThat(runnerSpy.getInvokedTestClass(), is(equalTo((Class) OuterClass.InnerClassWithOwnRunWith.class)));
}

@Test
public void memberClassDeepInsideAnnotatedMemberClass_isRunWithParentMemberClassRunner() throws Exception {
Runner runner = builder.runnerForClass(OuterClass.InnerClassWithOwnRunWith.MostInnerClass.class);
assertThat(runner, is(instanceOf(InnerRunner.class)));

RunnerSpy runnerSpy = (RunnerSpy) runner;
assertThat(runnerSpy.getInvokedTestClass(), is(equalTo((Class) OuterClass.InnerClassWithOwnRunWith.MostInnerClass.class)));
}

@RunWith(RunnerSpy.class)
public static class OuterClass {
public class InnerClassWithoutOwnRunWith {
@Test
public void test() {
}

public class MostInnerClass {
@Test
public void test() {
}
}
}

@RunWith(InnerRunner.class)
public class InnerClassWithOwnRunWith {
@Test
public void test() {
}

public class MostInnerClass {
@Test
public void test() {
}
}
}
}

public static class InnerRunner extends RunnerSpy {
public InnerRunner(Class testClass) {
super(testClass);
}

public InnerRunner(Class testClass, RunnerBuilder runnerBuilder) {
super(testClass, runnerBuilder);
}
}
}
37 changes: 37 additions & 0 deletions src/test/java/org/junit/runner/RunnerSpy.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package org.junit.runner;

import org.junit.runner.notification.RunNotifier;
import org.junit.runners.model.RunnerBuilder;

public class RunnerSpy extends Runner {
public static final Description DESCRIPTION = Description.TEST_MECHANISM;

private RunnerBuilder invokedRunnerBuilder;
private Class invokedTestClass;

public RunnerSpy(Class testClass) {
invokedTestClass = testClass;
}

public RunnerSpy(Class testClass, RunnerBuilder runnerBuilder) {
invokedTestClass = testClass;
invokedRunnerBuilder = runnerBuilder;
}

@Override
public Description getDescription() {
return DESCRIPTION;
}

@Override
public void run(RunNotifier runNotifier) {
}

public RunnerBuilder getInvokedRunnerBuilder() {
return invokedRunnerBuilder;
}

public Class getInvokedTestClass() {
return invokedTestClass;
}
}
11 changes: 11 additions & 0 deletions src/test/java/org/junit/runners/model/RunnerBuilderStub.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package org.junit.runners.model;

import org.junit.runner.Runner;
import org.junit.runner.RunnerSpy;

public class RunnerBuilderStub extends RunnerBuilder {
@Override
public Runner runnerForClass(Class<?> testClass) throws Throwable {
return new RunnerSpy(testClass, this);
}
}