Skip to content

Commit

Permalink
alpha-ready implementation of SuiteBuilder
Browse files Browse the repository at this point in the history
  • Loading branch information
dsaff committed May 12, 2010
1 parent 3b7ab4a commit f09cff7
Show file tree
Hide file tree
Showing 10 changed files with 188 additions and 119 deletions.
2 changes: 1 addition & 1 deletion build.xml
Expand Up @@ -5,7 +5,7 @@
<property name="src" value="src/main/java" />
<property name="target" location="target" />
<property name="bin" location="${target}/main" />
<property name="version-base" value="4.8.2" />
<property name="version-base" value="4.9-SNAPSHOT-20100512-0041" />
<property name="version-status" value="" />
<property name="version" value="${version-base}${version-status}" />
<property name="dist" value="junit${version}" />
Expand Down
4 changes: 4 additions & 0 deletions doc/ReleaseNotes4.9-SNAPSHOT-20100512-0041.html
@@ -0,0 +1,4 @@

Can't open /Users/saff/Documents/git-repos/junit/doc/ReleaseNotes4.9-SNAPSHOT-20100512-0041.txt: No such file or directory at build/Markdown.pl line 218.
Use of uninitialized value in substitution (s///) at build/Markdown.pl line 245.
Use of uninitialized value in substitution (s///) at build/Markdown.pl line 246.
28 changes: 28 additions & 0 deletions doc/ReleaseNotes4.9.html
@@ -0,0 +1,28 @@
<h2>Summary of Changes in version 4.9</h2>

<h3>SuiteBuilder</h3>

<p>A new way of declaring suites to run. SuiteBuilder allows for flexible
specification of where to find the classes containing tests, and how
to filter the resulting runners. This suite class lists the
explicit test classes to consider running, and then filters it down
to only those tests or classes annotated with <code>@Category(Yes.class)</code>:</p>

<pre><code>@RunWith(SuiteBuilder.class)
public static class OnlyYes {
@Classes
public Listed classes= new Listed(Yes1.class, Yes2.class, No1.class);

@RunnerFilter
public CategoryFilter filter= CategoryFilter.include(Yes.class);
}
</code></pre>

<p>We hope to soon include other implementations for the @Classes annotation,
including a classpath-searching test gatherer.</p>

<h3>Bug fixes</h3>

<ul>
<li>github#98: assumeTrue() does not work with expected exceptions</li>
</ul>
20 changes: 20 additions & 0 deletions doc/ReleaseNotes4.9.txt
@@ -1,5 +1,25 @@
## Summary of Changes in version 4.9 ##

### SuiteBuilder ###

A new way of declaring suites to run. SuiteBuilder allows for flexible
specification of where to find the classes containing tests, and how
to filter the resulting runners. This suite class lists the
explicit test classes to consider running, and then filters it down
to only those tests or classes annotated with `@Category(Yes.class)`:

@RunWith(SuiteBuilder.class)
public static class OnlyYes {
@Classes
public Listed classes= new Listed(Yes1.class, Yes2.class, No1.class);

@RunnerFilter
public CategoryFilter filter= CategoryFilter.include(Yes.class);
}

We hope to soon include other implementations for the @Classes annotation,
including a classpath-searching test gatherer.

### Bug fixes ###

- github#98: assumeTrue() does not work with expected exceptions
2 changes: 1 addition & 1 deletion src/main/java/junit/runner/Version.java
Expand Up @@ -9,7 +9,7 @@ private Version() {
}

public static String id() {
return "4.8.2";
return "4.9-SNAPSHOT-20100512-0041";
}

public static void main(String[] args) {
Expand Down
Expand Up @@ -8,10 +8,10 @@
import java.util.Arrays;
import java.util.List;

import org.junit.experimental.runners.SuiteBuilder;
import org.junit.runner.Runner;
import org.junit.tests.experimental.categories.SuiteBuilderTest.RunnerFilter;

public class CategoryFilter implements RunnerFilter.Value {
public class CategoryFilter implements SuiteBuilder.RunnerFilter.Value {
private final Class<?> fIncluded;

public CategoryFilter(Class<?> included) {
Expand Down
19 changes: 19 additions & 0 deletions src/main/java/org/junit/experimental/runners/Listed.java
@@ -0,0 +1,19 @@
/**
*
*/
package org.junit.experimental.runners;

import java.util.Arrays;
import java.util.List;

public class Listed implements SuiteBuilder.Classes.Value {
private final Class<?>[] fClasses;

public Listed(Class<?>... classes) {
fClasses= classes;
}

public List<? extends Class<?>> get() {
return Arrays.asList(fClasses);
}
}
99 changes: 99 additions & 0 deletions src/main/java/org/junit/experimental/runners/SuiteBuilder.java
@@ -0,0 +1,99 @@
/**
*
*/
package org.junit.experimental.runners;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import org.junit.internal.builders.AllDefaultPossibilitiesBuilder;
import org.junit.runner.Description;
import org.junit.runner.Runner;
import org.junit.runner.notification.RunNotifier;
import org.junit.runners.model.InitializationError;
import org.junit.runners.model.TestClass;

public class SuiteBuilder extends Runner {
@Retention(RetentionPolicy.RUNTIME)
public @interface RunnerFilter {
public static interface Value {
public abstract List<Runner> matchingRunners(
List<Runner> allPossibleRunners);
}
}

@Retention(RetentionPolicy.RUNTIME)
public @interface Classes {
public static interface Value {
public abstract Collection<? extends Class<?>> get();
}
}

private final TestClass fTestClass;
private final Object fInstance;
private final List<Runner> fRunners;

public SuiteBuilder(Class<?> testClass) throws InitializationError {
fTestClass= new TestClass(testClass);
fInstance = createInstance();
fRunners= computeRunners();
}

private Object createInstance() throws InitializationError {
try {
return fTestClass.getOnlyConstructor().newInstance();
} catch (Exception e) {
throw new InitializationError(e);
}
}

@Override
public Description getDescription() {
Description description= Description.createSuiteDescription(fTestClass.getJavaClass());
for (Runner each : fRunners) {
description.addChild(each.getDescription());
}
return description;
}

@Override
public void run(RunNotifier notifier) {
for (Runner each : fRunners)
each.run(notifier);
}

private List<Runner> computeRunners() throws InitializationError {
List<Class<?>> allPossibleClasses= gatherClasses();
List<Runner> allPossibleRunners= runnersForClasses(allPossibleClasses);
return filterRunners(allPossibleRunners);
}

private List<Runner> filterRunners(List<Runner> allPossibleRunners) {
List<Runner> result= allPossibleRunners;
for (SuiteBuilder.RunnerFilter.Value each : getFilters())
result= each.matchingRunners(result);
return result;
}

private List<SuiteBuilder.RunnerFilter.Value> getFilters() {
return fTestClass.getAnnotatedFieldValues(fInstance,
SuiteBuilder.RunnerFilter.class, SuiteBuilder.RunnerFilter.Value.class);
}

private List<Runner> runnersForClasses(List<Class<?>> allPossibleClasses) throws InitializationError {
return new AllDefaultPossibilitiesBuilder(true).runners(fTestClass
.getJavaClass(), allPossibleClasses);
}

public List<Class<?>> gatherClasses() {
ArrayList<Class<?>> result= new ArrayList<Class<?>>();
List<SuiteBuilder.Classes.Value> classeses= fTestClass.getAnnotatedFieldValues(
fInstance, SuiteBuilder.Classes.class, SuiteBuilder.Classes.Value.class);
for (SuiteBuilder.Classes.Value each : classeses)
result.addAll(each.get());
return result;
}
}
5 changes: 5 additions & 0 deletions src/main/java/org/junit/runners/model/RunnerBuilder.java
Expand Up @@ -87,6 +87,11 @@ public List<Runner> runners(Class<?> parent, Class<?>[] children)
}
}

public List<Runner> runners(Class<?> parent, List<Class<?>> children)
throws InitializationError {
return runners(parent, children.toArray(new Class<?>[0]));
}

private List<Runner> runners(Class<?>[] children) {
ArrayList<Runner> runners= new ArrayList<Runner>();
for (Class<?> each : children) {
Expand Down
Expand Up @@ -5,138 +5,32 @@
import static org.junit.experimental.results.PrintableResult.testResult;
import static org.junit.experimental.results.ResultMatchers.isSuccessful;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;

import org.junit.Test;
import org.junit.experimental.categories.Category;
import org.junit.experimental.categories.CategoryFilter;
import org.junit.experimental.runners.Listed;
import org.junit.experimental.runners.SuiteBuilder;
import org.junit.experimental.runners.SuiteBuilder.Classes;
import org.junit.experimental.runners.SuiteBuilder.RunnerFilter;
import org.junit.runner.Description;
import org.junit.runner.JUnitCore;
import org.junit.runner.Result;
import org.junit.runner.RunWith;
import org.junit.runner.Runner;
import org.junit.runner.notification.RunNotifier;
import org.junit.runners.BlockJUnit4ClassRunner;
import org.junit.runners.model.InitializationError;
import org.junit.runners.model.TestClass;

public class SuiteBuilderTest {
@Retention(RetentionPolicy.RUNTIME)
public @interface RunnerFilter {
public static interface Value {
public abstract List<Runner> matchingRunners(
List<Runner> allPossibleRunners);
}
}

public static class Listed implements Classes.Value {
private final Class<?>[] fClasses;

public Listed(Class<?>... classes) {
fClasses= classes;
}

public List<? extends Class<?>> get() {
return Arrays.asList(fClasses);
}
}

// Classes -> RunnerBuilder

@Retention(RetentionPolicy.RUNTIME)
public @interface Classes {
public static interface Value {
public abstract Collection<? extends Class<?>> get();
}
}

public static class SuiteBuilder extends Runner {
private final TestClass fTestClass;

private final Object fInstance;

private final List<Runner> fRunners;

public SuiteBuilder(Class<?> testClass) throws InitializationError {
fTestClass= new TestClass(testClass);
// TODO: extract complexity
try {
fInstance= fTestClass.getOnlyConstructor().newInstance();
} catch (Exception e) {
throw new InitializationError(e);
}
fRunners= computeRunners();
}

@Override
public Description getDescription() {
Description description= Description.createSuiteDescription(fTestClass.getJavaClass());
for (Runner each : fRunners) {
description.addChild(each.getDescription());
}
return description;
}

// TODO: require an instance?
@Override
public void run(RunNotifier notifier) {
for (Runner each : fRunners)
each.run(notifier);
}

private List<Runner> computeRunners() {
List<Class<?>> allPossibleClasses= gatherClasses();
List<Runner> allPossibleRunners= runnersForClasses(allPossibleClasses);
return filterRunners(allPossibleRunners);
}

private List<Runner> filterRunners(List<Runner> allPossibleRunners) {
List<Runner> result= allPossibleRunners;
for (RunnerFilter.Value each : getFilters())
result= each.matchingRunners(result);
return result;
}

private List<RunnerFilter.Value> getFilters() {
return fTestClass.getAnnotatedFieldValues(fInstance,
RunnerFilter.class, RunnerFilter.Value.class);
}

private List<Runner> runnersForClasses(List<Class<?>> allPossibleClasses) {
// TODO: cheating
ArrayList<Runner> result= new ArrayList<Runner>();
for (Class<?> each : allPossibleClasses) {
try {
result.add(new BlockJUnit4ClassRunner(each));
} catch (InitializationError e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
return result;
}

private List<Class<?>> gatherClasses() {
ArrayList<Class<?>> result= new ArrayList<Class<?>>();
List<Classes.Value> classeses= fTestClass.getAnnotatedFieldValues(
fInstance, Classes.class, Classes.Value.class);
for (Classes.Value each : classeses)
result.addAll(each.get());
return result;
}
}

static class Yes {
}

static class No {
}

// TODO: test multiple filters, multiple class sources

@Category(Yes.class)
public static class Yes1 {
@Test
Expand Down Expand Up @@ -168,7 +62,7 @@ public static class OnlyYesJustOne {
}

@RunWith(SuiteBuilder.class)
public static class OnlyYesMaybeTwo {
public static class OnlyYes {
@Classes
public Listed classes= new Listed(Yes1.class, Yes2.class, No1.class);

Expand Down Expand Up @@ -212,9 +106,9 @@ public void onlyRunOne() {

@Test
public void runTwo() {
Result result= new JUnitCore().run(OnlyYesMaybeTwo.class);
Result result= new JUnitCore().run(OnlyYes.class);
assertEquals(2, result.getRunCount());
assertThat(testResult(OnlyYesMaybeTwo.class), isSuccessful());
assertThat(testResult(OnlyYes.class), isSuccessful());
}

@Test
Expand Down

0 comments on commit f09cff7

Please sign in to comment.