aboutsummaryrefslogtreecommitdiff
path: root/src/main/java/org/junit/experimental
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/org/junit/experimental')
-rw-r--r--src/main/java/org/junit/experimental/ParallelComputer.java78
-rw-r--r--src/main/java/org/junit/experimental/categories/Categories.java192
-rw-r--r--src/main/java/org/junit/experimental/categories/Category.java43
-rw-r--r--src/main/java/org/junit/experimental/max/CouldNotReadCoreException.java15
-rw-r--r--src/main/java/org/junit/experimental/max/MaxCore.java170
-rw-r--r--src/main/java/org/junit/experimental/max/MaxHistory.java166
-rw-r--r--src/main/java/org/junit/experimental/results/FailureList.java31
-rw-r--r--src/main/java/org/junit/experimental/results/PrintableResult.java63
-rw-r--r--src/main/java/org/junit/experimental/results/ResultMatchers.java70
-rw-r--r--src/main/java/org/junit/experimental/runners/Enclosed.java31
-rw-r--r--src/main/java/org/junit/experimental/theories/DataPoint.java9
-rw-r--r--src/main/java/org/junit/experimental/theories/DataPoints.java9
-rw-r--r--src/main/java/org/junit/experimental/theories/ParameterSignature.java90
-rw-r--r--src/main/java/org/junit/experimental/theories/ParameterSupplier.java8
-rw-r--r--src/main/java/org/junit/experimental/theories/ParametersSuppliedBy.java12
-rw-r--r--src/main/java/org/junit/experimental/theories/PotentialAssignment.java31
-rw-r--r--src/main/java/org/junit/experimental/theories/Theories.java199
-rw-r--r--src/main/java/org/junit/experimental/theories/Theory.java12
-rw-r--r--src/main/java/org/junit/experimental/theories/internal/AllMembersSupplier.java127
-rw-r--r--src/main/java/org/junit/experimental/theories/internal/Assignments.java133
-rw-r--r--src/main/java/org/junit/experimental/theories/internal/ParameterizedAssertionError.java49
-rw-r--r--src/main/java/org/junit/experimental/theories/suppliers/TestedOn.java13
-rw-r--r--src/main/java/org/junit/experimental/theories/suppliers/TestedOnSupplier.java23
23 files changed, 1574 insertions, 0 deletions
diff --git a/src/main/java/org/junit/experimental/ParallelComputer.java b/src/main/java/org/junit/experimental/ParallelComputer.java
new file mode 100644
index 0000000..fccb97c
--- /dev/null
+++ b/src/main/java/org/junit/experimental/ParallelComputer.java
@@ -0,0 +1,78 @@
+package org.junit.experimental;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+
+import org.junit.runner.Computer;
+import org.junit.runner.Runner;
+import org.junit.runners.ParentRunner;
+import org.junit.runners.model.InitializationError;
+import org.junit.runners.model.RunnerBuilder;
+import org.junit.runners.model.RunnerScheduler;
+
+public class ParallelComputer extends Computer {
+ private final boolean fClasses;
+
+ private final boolean fMethods;
+
+ public ParallelComputer(boolean classes, boolean methods) {
+ fClasses= classes;
+ fMethods= methods;
+ }
+
+ public static Computer classes() {
+ return new ParallelComputer(true, false);
+ }
+
+ public static Computer methods() {
+ return new ParallelComputer(false, true);
+ }
+
+ private static <T> Runner parallelize(Runner runner) {
+ if (runner instanceof ParentRunner<?>) {
+ ((ParentRunner<?>) runner).setScheduler(new RunnerScheduler() {
+ private final List<Future<Object>> fResults= new ArrayList<Future<Object>>();
+
+ private final ExecutorService fService= Executors
+ .newCachedThreadPool();
+
+ public void schedule(final Runnable childStatement) {
+ fResults.add(fService.submit(new Callable<Object>() {
+ public Object call() throws Exception {
+ childStatement.run();
+ return null;
+ }
+ }));
+ }
+
+ public void finished() {
+ for (Future<Object> each : fResults)
+ try {
+ each.get();
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+ });
+ }
+ return runner;
+ }
+
+ @Override
+ public Runner getSuite(RunnerBuilder builder, java.lang.Class<?>[] classes)
+ throws InitializationError {
+ Runner suite= super.getSuite(builder, classes);
+ return fClasses ? parallelize(suite) : suite;
+ }
+
+ @Override
+ protected Runner getRunner(RunnerBuilder builder, Class<?> testClass)
+ throws Throwable {
+ Runner runner= super.getRunner(builder, testClass);
+ return fMethods ? parallelize(runner) : runner;
+ }
+}
diff --git a/src/main/java/org/junit/experimental/categories/Categories.java b/src/main/java/org/junit/experimental/categories/Categories.java
new file mode 100644
index 0000000..d57b4d3
--- /dev/null
+++ b/src/main/java/org/junit/experimental/categories/Categories.java
@@ -0,0 +1,192 @@
+/**
+ *
+ */
+package org.junit.experimental.categories;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import org.junit.runner.Description;
+import org.junit.runner.manipulation.Filter;
+import org.junit.runner.manipulation.NoTestsRemainException;
+import org.junit.runners.Suite;
+import org.junit.runners.model.InitializationError;
+import org.junit.runners.model.RunnerBuilder;
+
+/**
+ * From a given set of test classes, runs only the classes and methods that are
+ * annotated with either the category given with the @IncludeCategory
+ * annotation, or a subtype of that category.
+ *
+ * Note that, for now, annotating suites with {@code @Category} has no effect.
+ * Categories must be annotated on the direct method or class.
+ *
+ * Example:
+ *
+ * <pre>
+ * public interface FastTests {
+ * }
+ *
+ * public interface SlowTests {
+ * }
+ *
+ * public static class A {
+ * &#064;Test
+ * public void a() {
+ * fail();
+ * }
+ *
+ * &#064;Category(SlowTests.class)
+ * &#064;Test
+ * public void b() {
+ * }
+ * }
+ *
+ * &#064;Category( { SlowTests.class, FastTests.class })
+ * public static class B {
+ * &#064;Test
+ * public void c() {
+ *
+ * }
+ * }
+ *
+ * &#064;RunWith(Categories.class)
+ * &#064;IncludeCategory(SlowTests.class)
+ * &#064;SuiteClasses( { A.class, B.class })
+ * // Note that Categories is a kind of Suite
+ * public static class SlowTestSuite {
+ * }
+ * </pre>
+ */
+public class Categories extends Suite {
+ // the way filters are implemented makes this unnecessarily complicated,
+ // buggy, and difficult to specify. A new way of handling filters could
+ // someday enable a better new implementation.
+ // https://github.com/KentBeck/junit/issues/issue/172
+
+ @Retention(RetentionPolicy.RUNTIME)
+ public @interface IncludeCategory {
+ public Class<?> value();
+ }
+
+ @Retention(RetentionPolicy.RUNTIME)
+ public @interface ExcludeCategory {
+ public Class<?> value();
+ }
+
+ public static class CategoryFilter extends Filter {
+ public static CategoryFilter include(Class<?> categoryType) {
+ return new CategoryFilter(categoryType, null);
+ }
+
+ private final Class<?> fIncluded;
+
+ private final Class<?> fExcluded;
+
+ public CategoryFilter(Class<?> includedCategory,
+ Class<?> excludedCategory) {
+ fIncluded= includedCategory;
+ fExcluded= excludedCategory;
+ }
+
+ @Override
+ public String describe() {
+ return "category " + fIncluded;
+ }
+
+ @Override
+ public boolean shouldRun(Description description) {
+ if (hasCorrectCategoryAnnotation(description))
+ return true;
+ for (Description each : description.getChildren())
+ if (shouldRun(each))
+ return true;
+ return false;
+ }
+
+ private boolean hasCorrectCategoryAnnotation(Description description) {
+ List<Class<?>> categories= categories(description);
+ if (categories.isEmpty())
+ return fIncluded == null;
+ for (Class<?> each : categories)
+ if (fExcluded != null && fExcluded.isAssignableFrom(each))
+ return false;
+ for (Class<?> each : categories)
+ if (fIncluded == null || fIncluded.isAssignableFrom(each))
+ return true;
+ return false;
+ }
+
+ private List<Class<?>> categories(Description description) {
+ ArrayList<Class<?>> categories= new ArrayList<Class<?>>();
+ categories.addAll(Arrays.asList(directCategories(description)));
+ categories.addAll(Arrays.asList(directCategories(parentDescription(description))));
+ return categories;
+ }
+
+ private Description parentDescription(Description description) {
+ Class<?> testClass= description.getTestClass();
+ if (testClass == null)
+ return null;
+ return Description.createSuiteDescription(testClass);
+ }
+
+ private Class<?>[] directCategories(Description description) {
+ if (description == null)
+ return new Class<?>[0];
+ Category annotation= description.getAnnotation(Category.class);
+ if (annotation == null)
+ return new Class<?>[0];
+ return annotation.value();
+ }
+ }
+
+ public Categories(Class<?> klass, RunnerBuilder builder)
+ throws InitializationError {
+ super(klass, builder);
+ try {
+ filter(new CategoryFilter(getIncludedCategory(klass),
+ getExcludedCategory(klass)));
+ } catch (NoTestsRemainException e) {
+ throw new InitializationError(e);
+ }
+ assertNoCategorizedDescendentsOfUncategorizeableParents(getDescription());
+ }
+
+ private Class<?> getIncludedCategory(Class<?> klass) {
+ IncludeCategory annotation= klass.getAnnotation(IncludeCategory.class);
+ return annotation == null ? null : annotation.value();
+ }
+
+ private Class<?> getExcludedCategory(Class<?> klass) {
+ ExcludeCategory annotation= klass.getAnnotation(ExcludeCategory.class);
+ return annotation == null ? null : annotation.value();
+ }
+
+ private void assertNoCategorizedDescendentsOfUncategorizeableParents(Description description) throws InitializationError {
+ if (!canHaveCategorizedChildren(description))
+ assertNoDescendantsHaveCategoryAnnotations(description);
+ for (Description each : description.getChildren())
+ assertNoCategorizedDescendentsOfUncategorizeableParents(each);
+ }
+
+ private void assertNoDescendantsHaveCategoryAnnotations(Description description) throws InitializationError {
+ for (Description each : description.getChildren()) {
+ if (each.getAnnotation(Category.class) != null)
+ throw new InitializationError("Category annotations on Parameterized classes are not supported on individual methods.");
+ assertNoDescendantsHaveCategoryAnnotations(each);
+ }
+ }
+
+ // If children have names like [0], our current magical category code can't determine their
+ // parentage.
+ private static boolean canHaveCategorizedChildren(Description description) {
+ for (Description each : description.getChildren())
+ if (each.getTestClass() == null)
+ return false;
+ return true;
+ }
+} \ No newline at end of file
diff --git a/src/main/java/org/junit/experimental/categories/Category.java b/src/main/java/org/junit/experimental/categories/Category.java
new file mode 100644
index 0000000..3a4c0b9
--- /dev/null
+++ b/src/main/java/org/junit/experimental/categories/Category.java
@@ -0,0 +1,43 @@
+package org.junit.experimental.categories;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Marks a test class or test method as belonging to one or more categories of tests.
+ * The value is an array of arbitrary classes.
+ *
+ * This annotation is only interpreted by the Categories runner (at present).
+ *
+ * For example:
+<pre>
+ public interface FastTests {}
+ public interface SlowTests {}
+
+ public static class A {
+ &#064;Test
+ public void a() {
+ fail();
+ }
+
+ &#064;Category(SlowTests.class)
+ &#064;Test
+ public void b() {
+ }
+ }
+
+ &#064;Category({SlowTests.class, FastTests.class})
+ public static class B {
+ &#064;Test
+ public void c() {
+
+ }
+ }
+</pre>
+ *
+ * For more usage, see code example on {@link Categories}.
+ */
+@Retention(RetentionPolicy.RUNTIME)
+public @interface Category {
+ Class<?>[] value();
+} \ No newline at end of file
diff --git a/src/main/java/org/junit/experimental/max/CouldNotReadCoreException.java b/src/main/java/org/junit/experimental/max/CouldNotReadCoreException.java
new file mode 100644
index 0000000..03c3c8c
--- /dev/null
+++ b/src/main/java/org/junit/experimental/max/CouldNotReadCoreException.java
@@ -0,0 +1,15 @@
+package org.junit.experimental.max;
+
+/**
+ * Thrown when Max cannot read the MaxCore serialization
+ */
+public class CouldNotReadCoreException extends Exception {
+ private static final long serialVersionUID= 1L;
+
+ /**
+ * Constructs
+ */
+ public CouldNotReadCoreException(Throwable e) {
+ super(e);
+ }
+}
diff --git a/src/main/java/org/junit/experimental/max/MaxCore.java b/src/main/java/org/junit/experimental/max/MaxCore.java
new file mode 100644
index 0000000..a2a34a9
--- /dev/null
+++ b/src/main/java/org/junit/experimental/max/MaxCore.java
@@ -0,0 +1,170 @@
+package org.junit.experimental.max;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import junit.framework.TestSuite;
+
+import org.junit.internal.requests.SortingRequest;
+import org.junit.internal.runners.ErrorReportingRunner;
+import org.junit.internal.runners.JUnit38ClassRunner;
+import org.junit.runner.Description;
+import org.junit.runner.JUnitCore;
+import org.junit.runner.Request;
+import org.junit.runner.Result;
+import org.junit.runner.Runner;
+import org.junit.runners.Suite;
+import org.junit.runners.model.InitializationError;
+
+/**
+ * A replacement for JUnitCore, which keeps track of runtime and failure history, and reorders tests
+ * to maximize the chances that a failing test occurs early in the test run.
+ *
+ * The rules for sorting are:
+ * <ol>
+ * <li> Never-run tests first, in arbitrary order
+ * <li> Group remaining tests by the date at which they most recently failed.
+ * <li> Sort groups such that the most recent failure date is first, and never-failing tests are at the end.
+ * <li> Within a group, run the fastest tests first.
+ * </ol>
+ */
+public class MaxCore {
+ private static final String MALFORMED_JUNIT_3_TEST_CLASS_PREFIX= "malformed JUnit 3 test class: ";
+
+ /**
+ * Create a new MaxCore from a serialized file stored at storedResults
+ * @deprecated use storedLocally()
+ */
+ @Deprecated
+ public static MaxCore forFolder(String folderName) {
+ return storedLocally(new File(folderName));
+ }
+
+ /**
+ * Create a new MaxCore from a serialized file stored at storedResults
+ */
+ public static MaxCore storedLocally(File storedResults) {
+ return new MaxCore(storedResults);
+ }
+
+ private final MaxHistory fHistory;
+
+ private MaxCore(File storedResults) {
+ fHistory = MaxHistory.forFolder(storedResults);
+ }
+
+ /**
+ * Run all the tests in <code>class</code>.
+ * @return a {@link Result} describing the details of the test run and the failed tests.
+ */
+ public Result run(Class<?> testClass) {
+ return run(Request.aClass(testClass));
+ }
+
+ /**
+ * Run all the tests contained in <code>request</code>.
+ * @param request the request describing tests
+ * @return a {@link Result} describing the details of the test run and the failed tests.
+ */
+ public Result run(Request request) {
+ return run(request, new JUnitCore());
+ }
+
+ /**
+ * Run all the tests contained in <code>request</code>.
+ *
+ * This variant should be used if {@code core} has attached listeners that this
+ * run should notify.
+ *
+ * @param request the request describing tests
+ * @param core a JUnitCore to delegate to.
+ * @return a {@link Result} describing the details of the test run and the failed tests.
+ */
+ public Result run(Request request, JUnitCore core) {
+ core.addListener(fHistory.listener());
+ return core.run(sortRequest(request).getRunner());
+ }
+
+ /**
+ * @param request
+ * @return a new Request, which contains all of the same tests, but in a new order.
+ */
+ public Request sortRequest(Request request) {
+ if (request instanceof SortingRequest) // We'll pay big karma points for this
+ return request;
+ List<Description> leaves= findLeaves(request);
+ Collections.sort(leaves, fHistory.testComparator());
+ return constructLeafRequest(leaves);
+ }
+
+ private Request constructLeafRequest(List<Description> leaves) {
+ final List<Runner> runners = new ArrayList<Runner>();
+ for (Description each : leaves)
+ runners.add(buildRunner(each));
+ return new Request() {
+ @Override
+ public Runner getRunner() {
+ try {
+ return new Suite((Class<?>)null, runners) {};
+ } catch (InitializationError e) {
+ return new ErrorReportingRunner(null, e);
+ }
+ }
+ };
+ }
+
+ private Runner buildRunner(Description each) {
+ if (each.toString().equals("TestSuite with 0 tests"))
+ return Suite.emptySuite();
+ if (each.toString().startsWith(MALFORMED_JUNIT_3_TEST_CLASS_PREFIX))
+ // This is cheating, because it runs the whole class
+ // to get the warning for this method, but we can't do better,
+ // because JUnit 3.8's
+ // thrown away which method the warning is for.
+ return new JUnit38ClassRunner(new TestSuite(getMalformedTestClass(each)));
+ Class<?> type= each.getTestClass();
+ if (type == null)
+ throw new RuntimeException("Can't build a runner from description [" + each + "]");
+ String methodName= each.getMethodName();
+ if (methodName == null)
+ return Request.aClass(type).getRunner();
+ return Request.method(type, methodName).getRunner();
+ }
+
+ private Class<?> getMalformedTestClass(Description each) {
+ try {
+ return Class.forName(each.toString().replace(MALFORMED_JUNIT_3_TEST_CLASS_PREFIX, ""));
+ } catch (ClassNotFoundException e) {
+ return null;
+ }
+ }
+
+ /**
+ * @param request a request to run
+ * @return a list of method-level tests to run, sorted in the order
+ * specified in the class comment.
+ */
+ public List<Description> sortedLeavesForTest(Request request) {
+ return findLeaves(sortRequest(request));
+ }
+
+ private List<Description> findLeaves(Request request) {
+ List<Description> results= new ArrayList<Description>();
+ findLeaves(null, request.getRunner().getDescription(), results);
+ return results;
+ }
+
+ private void findLeaves(Description parent, Description description, List<Description> results) {
+ if (description.getChildren().isEmpty())
+ if (description.toString().equals("warning(junit.framework.TestSuite$1)"))
+ results.add(Description.createSuiteDescription(MALFORMED_JUNIT_3_TEST_CLASS_PREFIX + parent));
+ else
+ results.add(description);
+ else
+ for (Description each : description.getChildren())
+ findLeaves(description, each, results);
+ }
+}
+
diff --git a/src/main/java/org/junit/experimental/max/MaxHistory.java b/src/main/java/org/junit/experimental/max/MaxHistory.java
new file mode 100644
index 0000000..e091793
--- /dev/null
+++ b/src/main/java/org/junit/experimental/max/MaxHistory.java
@@ -0,0 +1,166 @@
+package org.junit.experimental.max;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.Serializable;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.junit.runner.Description;
+import org.junit.runner.Result;
+import org.junit.runner.notification.Failure;
+import org.junit.runner.notification.RunListener;
+
+/**
+ * Stores a subset of the history of each test:
+ * <ul>
+ * <li>Last failure timestamp
+ * <li>Duration of last execution
+ * </ul>
+ */
+public class MaxHistory implements Serializable {
+ private static final long serialVersionUID= 1L;
+
+ /**
+ * Loads a {@link MaxHistory} from {@code file}, or generates a new one that
+ * will be saved to {@code file}.
+ */
+ public static MaxHistory forFolder(File file) {
+ if (file.exists())
+ try {
+ return readHistory(file);
+ } catch (CouldNotReadCoreException e) {
+ e.printStackTrace();
+ file.delete();
+ }
+ return new MaxHistory(file);
+ }
+
+ private static MaxHistory readHistory(File storedResults)
+ throws CouldNotReadCoreException {
+ try {
+ FileInputStream file= new FileInputStream(storedResults);
+ try {
+ ObjectInputStream stream= new ObjectInputStream(file);
+ try {
+ return (MaxHistory) stream.readObject();
+ } finally {
+ stream.close();
+ }
+ } finally {
+ file.close();
+ }
+ } catch (Exception e) {
+ throw new CouldNotReadCoreException(e);
+ }
+ }
+
+ private final Map<String, Long> fDurations= new HashMap<String, Long>();
+
+ private final Map<String, Long> fFailureTimestamps= new HashMap<String, Long>();
+
+ private final File fHistoryStore;
+
+ private MaxHistory(File storedResults) {
+ fHistoryStore= storedResults;
+ }
+
+ private void save() throws IOException {
+ ObjectOutputStream stream= new ObjectOutputStream(new FileOutputStream(
+ fHistoryStore));
+ stream.writeObject(this);
+ stream.close();
+ }
+
+ Long getFailureTimestamp(Description key) {
+ return fFailureTimestamps.get(key.toString());
+ }
+
+ void putTestFailureTimestamp(Description key, long end) {
+ fFailureTimestamps.put(key.toString(), end);
+ }
+
+ boolean isNewTest(Description key) {
+ return !fDurations.containsKey(key.toString());
+ }
+
+ Long getTestDuration(Description key) {
+ return fDurations.get(key.toString());
+ }
+
+ void putTestDuration(Description description, long duration) {
+ fDurations.put(description.toString(), duration);
+ }
+
+ private final class RememberingListener extends RunListener {
+ private long overallStart= System.currentTimeMillis();
+
+ private Map<Description, Long> starts= new HashMap<Description, Long>();
+
+ @Override
+ public void testStarted(Description description) throws Exception {
+ starts.put(description, System.nanoTime()); // Get most accurate
+ // possible time
+ }
+
+ @Override
+ public void testFinished(Description description) throws Exception {
+ long end= System.nanoTime();
+ long start= starts.get(description);
+ putTestDuration(description, end - start);
+ }
+
+ @Override
+ public void testFailure(Failure failure) throws Exception {
+ putTestFailureTimestamp(failure.getDescription(), overallStart);
+ }
+
+ @Override
+ public void testRunFinished(Result result) throws Exception {
+ save();
+ }
+ }
+
+ private class TestComparator implements Comparator<Description> {
+ public int compare(Description o1, Description o2) {
+ // Always prefer new tests
+ if (isNewTest(o1))
+ return -1;
+ if (isNewTest(o2))
+ return 1;
+ // Then most recently failed first
+ int result= getFailure(o2).compareTo(getFailure(o1));
+ return result != 0 ? result
+ // Then shorter tests first
+ : getTestDuration(o1).compareTo(getTestDuration(o2));
+ }
+
+ private Long getFailure(Description key) {
+ Long result= getFailureTimestamp(key);
+ if (result == null)
+ return 0L; // 0 = "never failed (that I know about)"
+ return result;
+ }
+ }
+
+ /**
+ * @return a listener that will update this history based on the test
+ * results reported.
+ */
+ public RunListener listener() {
+ return new RememberingListener();
+ }
+
+ /**
+ * @return a comparator that ranks tests based on the JUnit Max sorting
+ * rules, as described in the {@link MaxCore} class comment.
+ */
+ public Comparator<Description> testComparator() {
+ return new TestComparator();
+ }
+}
diff --git a/src/main/java/org/junit/experimental/results/FailureList.java b/src/main/java/org/junit/experimental/results/FailureList.java
new file mode 100644
index 0000000..f4bc9b7
--- /dev/null
+++ b/src/main/java/org/junit/experimental/results/FailureList.java
@@ -0,0 +1,31 @@
+/**
+ *
+ */
+package org.junit.experimental.results;
+
+import java.util.List;
+
+import org.junit.runner.Result;
+import org.junit.runner.notification.Failure;
+import org.junit.runner.notification.RunListener;
+
+class FailureList {
+ private final List<Failure> failures;
+
+ public FailureList(List<Failure> failures) {
+ this.failures= failures;
+ }
+
+ public Result result() {
+ Result result= new Result();
+ RunListener listener= result.createListener();
+ for (Failure failure : failures) {
+ try {
+ listener.testFailure(failure);
+ } catch (Exception e) {
+ throw new RuntimeException("I can't believe this happened");
+ }
+ }
+ return result;
+ }
+} \ No newline at end of file
diff --git a/src/main/java/org/junit/experimental/results/PrintableResult.java b/src/main/java/org/junit/experimental/results/PrintableResult.java
new file mode 100644
index 0000000..8bc6f54
--- /dev/null
+++ b/src/main/java/org/junit/experimental/results/PrintableResult.java
@@ -0,0 +1,63 @@
+package org.junit.experimental.results;
+
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
+import java.util.List;
+
+import org.junit.internal.TextListener;
+import org.junit.runner.JUnitCore;
+import org.junit.runner.Request;
+import org.junit.runner.Result;
+import org.junit.runner.notification.Failure;
+
+/**
+ * A test result that prints nicely in error messages.
+ * This is only intended to be used in JUnit self-tests.
+ * For example:
+ *
+ * <pre>
+ * assertThat(testResult(HasExpectedException.class), isSuccessful());
+ * </pre>
+ */
+public class PrintableResult {
+ /**
+ * The result of running JUnit on {@code type}
+ */
+ public static PrintableResult testResult(Class<?> type) {
+ return testResult(Request.aClass(type));
+ }
+
+ /**
+ * The result of running JUnit on Request {@code request}
+ */
+ public static PrintableResult testResult(Request request) {
+ return new PrintableResult(new JUnitCore().run(request));
+ }
+
+ private Result result;
+
+ /**
+ * A result that includes the given {@code failures}
+ */
+ public PrintableResult(List<Failure> failures) {
+ this(new FailureList(failures).result());
+ }
+
+ private PrintableResult(Result result) {
+ this.result = result;
+ }
+
+ @Override
+ public String toString() {
+ ByteArrayOutputStream stream = new ByteArrayOutputStream();
+ new TextListener(new PrintStream(stream)).testRunFinished(result);
+ return stream.toString();
+ }
+
+ /**
+ * Returns the number of failures in this result.
+ */
+ public int failureCount() {
+ return result.getFailures().size();
+ }
+} \ No newline at end of file
diff --git a/src/main/java/org/junit/experimental/results/ResultMatchers.java b/src/main/java/org/junit/experimental/results/ResultMatchers.java
new file mode 100644
index 0000000..220d0dc
--- /dev/null
+++ b/src/main/java/org/junit/experimental/results/ResultMatchers.java
@@ -0,0 +1,70 @@
+package org.junit.experimental.results;
+
+import org.hamcrest.BaseMatcher;
+import org.hamcrest.Description;
+import org.hamcrest.Matcher;
+import org.junit.internal.matchers.TypeSafeMatcher;
+
+/**
+ * Matchers on a PrintableResult, to enable JUnit self-tests.
+ * For example:
+ *
+ * <pre>
+ * assertThat(testResult(HasExpectedException.class), isSuccessful());
+ * </pre>
+ */
+public class ResultMatchers {
+ /**
+ * Matches if the tests are all successful
+ */
+ public static Matcher<PrintableResult> isSuccessful() {
+ return failureCountIs(0);
+ }
+
+ /**
+ * Matches if there are {@code count} failures
+ */
+ public static Matcher<PrintableResult> failureCountIs(final int count) {
+ return new TypeSafeMatcher<PrintableResult>() {
+ public void describeTo(Description description) {
+ description.appendText("has " + count + " failures");
+ }
+
+ @Override
+ public boolean matchesSafely(PrintableResult item) {
+ return item.failureCount() == count;
+ }
+ };
+ }
+
+ /**
+ * Matches if the result has exactly one failure, and it contains {@code string}
+ */
+ public static Matcher<Object> hasSingleFailureContaining(final String string) {
+ return new BaseMatcher<Object>() {
+ public boolean matches(Object item) {
+ return item.toString().contains(string) && failureCountIs(1).matches(item);
+ }
+
+ public void describeTo(Description description) {
+ description.appendText("has single failure containing " + string);
+ }
+ };
+ }
+
+ /**
+ * Matches if the result has one or more failures, and at least one of them
+ * contains {@code string}
+ */
+ public static Matcher<PrintableResult> hasFailureContaining(final String string) {
+ return new BaseMatcher<PrintableResult>() {
+ public boolean matches(Object item) {
+ return item.toString().contains(string);
+ }
+
+ public void describeTo(Description description) {
+ description.appendText("has failure containing " + string);
+ }
+ };
+ }
+}
diff --git a/src/main/java/org/junit/experimental/runners/Enclosed.java b/src/main/java/org/junit/experimental/runners/Enclosed.java
new file mode 100644
index 0000000..b0560ed
--- /dev/null
+++ b/src/main/java/org/junit/experimental/runners/Enclosed.java
@@ -0,0 +1,31 @@
+package org.junit.experimental.runners;
+
+import org.junit.runners.Suite;
+import org.junit.runners.model.RunnerBuilder;
+
+
+/**
+ * If you put tests in inner classes, Ant, for example, won't find them. By running the outer class
+ * with Enclosed, the tests in the inner classes will be run. You might put tests in inner classes
+ * to group them for convenience or to share constants.
+ *
+ * So, for example:
+ * <pre>
+ * \@RunWith(Enclosed.class)
+ * public class ListTests {
+ * ...useful shared stuff...
+ * public static class OneKindOfListTest {...}
+ * public static class AnotherKind {...}
+ * }
+ * </pre>
+ *
+ * For a real example, @see org.junit.tests.manipulation.SortableTest.
+ */
+public class Enclosed extends Suite {
+ /**
+ * Only called reflectively. Do not use programmatically.
+ */
+ public Enclosed(Class<?> klass, RunnerBuilder builder) throws Throwable {
+ super(builder, klass, klass.getClasses());
+ }
+}
diff --git a/src/main/java/org/junit/experimental/theories/DataPoint.java b/src/main/java/org/junit/experimental/theories/DataPoint.java
new file mode 100644
index 0000000..2aaba6a
--- /dev/null
+++ b/src/main/java/org/junit/experimental/theories/DataPoint.java
@@ -0,0 +1,9 @@
+package org.junit.experimental.theories;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+@Retention(RetentionPolicy.RUNTIME)
+public @interface DataPoint {
+
+}
diff --git a/src/main/java/org/junit/experimental/theories/DataPoints.java b/src/main/java/org/junit/experimental/theories/DataPoints.java
new file mode 100644
index 0000000..42145e3
--- /dev/null
+++ b/src/main/java/org/junit/experimental/theories/DataPoints.java
@@ -0,0 +1,9 @@
+package org.junit.experimental.theories;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+@Retention(RetentionPolicy.RUNTIME)
+public @interface DataPoints {
+
+}
diff --git a/src/main/java/org/junit/experimental/theories/ParameterSignature.java b/src/main/java/org/junit/experimental/theories/ParameterSignature.java
new file mode 100644
index 0000000..e7150fc
--- /dev/null
+++ b/src/main/java/org/junit/experimental/theories/ParameterSignature.java
@@ -0,0 +1,90 @@
+/**
+ *
+ */
+package org.junit.experimental.theories;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+public class ParameterSignature {
+ public static ArrayList<ParameterSignature> signatures(Method method) {
+ return signatures(method.getParameterTypes(), method
+ .getParameterAnnotations());
+ }
+
+ public static List<ParameterSignature> signatures(Constructor<?> constructor) {
+ return signatures(constructor.getParameterTypes(), constructor
+ .getParameterAnnotations());
+ }
+
+ private static ArrayList<ParameterSignature> signatures(
+ Class<?>[] parameterTypes, Annotation[][] parameterAnnotations) {
+ ArrayList<ParameterSignature> sigs= new ArrayList<ParameterSignature>();
+ for (int i= 0; i < parameterTypes.length; i++) {
+ sigs.add(new ParameterSignature(parameterTypes[i],
+ parameterAnnotations[i]));
+ }
+ return sigs;
+ }
+
+ private final Class<?> type;
+
+ private final Annotation[] annotations;
+
+ private ParameterSignature(Class<?> type, Annotation[] annotations) {
+ this.type= type;
+ this.annotations= annotations;
+ }
+
+ public boolean canAcceptType(Class<?> candidate) {
+ return type.isAssignableFrom(candidate);
+ }
+
+ public Class<?> getType() {
+ return type;
+ }
+
+ public List<Annotation> getAnnotations() {
+ return Arrays.asList(annotations);
+ }
+
+ public boolean canAcceptArrayType(Class<?> type) {
+ return type.isArray() && canAcceptType(type.getComponentType());
+ }
+
+ public boolean hasAnnotation(Class<? extends Annotation> type) {
+ return getAnnotation(type) != null;
+ }
+
+ public <T extends Annotation> T findDeepAnnotation(Class<T> annotationType) {
+ Annotation[] annotations2= annotations;
+ return findDeepAnnotation(annotations2, annotationType, 3);
+ }
+
+ private <T extends Annotation> T findDeepAnnotation(
+ Annotation[] annotations, Class<T> annotationType, int depth) {
+ if (depth == 0)
+ return null;
+ for (Annotation each : annotations) {
+ if (annotationType.isInstance(each))
+ return annotationType.cast(each);
+ Annotation candidate= findDeepAnnotation(each.annotationType()
+ .getAnnotations(), annotationType, depth - 1);
+ if (candidate != null)
+ return annotationType.cast(candidate);
+ }
+
+ return null;
+ }
+
+ public <T extends Annotation> T getAnnotation(Class<T> annotationType) {
+ for (Annotation each : getAnnotations())
+ if (annotationType.isInstance(each))
+ return annotationType.cast(each);
+ return null;
+ }
+} \ No newline at end of file
diff --git a/src/main/java/org/junit/experimental/theories/ParameterSupplier.java b/src/main/java/org/junit/experimental/theories/ParameterSupplier.java
new file mode 100644
index 0000000..9016c91
--- /dev/null
+++ b/src/main/java/org/junit/experimental/theories/ParameterSupplier.java
@@ -0,0 +1,8 @@
+package org.junit.experimental.theories;
+
+import java.util.List;
+
+
+public abstract class ParameterSupplier {
+ public abstract List<PotentialAssignment> getValueSources(ParameterSignature sig);
+}
diff --git a/src/main/java/org/junit/experimental/theories/ParametersSuppliedBy.java b/src/main/java/org/junit/experimental/theories/ParametersSuppliedBy.java
new file mode 100644
index 0000000..8f090ef
--- /dev/null
+++ b/src/main/java/org/junit/experimental/theories/ParametersSuppliedBy.java
@@ -0,0 +1,12 @@
+package org.junit.experimental.theories;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+
+@Retention(RetentionPolicy.RUNTIME)
+public @interface ParametersSuppliedBy {
+
+ Class<? extends ParameterSupplier> value();
+
+}
diff --git a/src/main/java/org/junit/experimental/theories/PotentialAssignment.java b/src/main/java/org/junit/experimental/theories/PotentialAssignment.java
new file mode 100644
index 0000000..0c008d0
--- /dev/null
+++ b/src/main/java/org/junit/experimental/theories/PotentialAssignment.java
@@ -0,0 +1,31 @@
+package org.junit.experimental.theories;
+
+public abstract class PotentialAssignment {
+ public static class CouldNotGenerateValueException extends Exception {
+ private static final long serialVersionUID= 1L;
+ }
+
+ public static PotentialAssignment forValue(final String name, final Object value) {
+ return new PotentialAssignment() {
+ @Override
+ public Object getValue() throws CouldNotGenerateValueException {
+ return value;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("[%s]", value);
+ }
+
+ @Override
+ public String getDescription()
+ throws CouldNotGenerateValueException {
+ return name;
+ }
+ };
+ }
+
+ public abstract Object getValue() throws CouldNotGenerateValueException;
+
+ public abstract String getDescription() throws CouldNotGenerateValueException;
+}
diff --git a/src/main/java/org/junit/experimental/theories/Theories.java b/src/main/java/org/junit/experimental/theories/Theories.java
new file mode 100644
index 0000000..82ff98b
--- /dev/null
+++ b/src/main/java/org/junit/experimental/theories/Theories.java
@@ -0,0 +1,199 @@
+/**
+ *
+ */
+package org.junit.experimental.theories;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.junit.Assert;
+import org.junit.experimental.theories.PotentialAssignment.CouldNotGenerateValueException;
+import org.junit.experimental.theories.internal.Assignments;
+import org.junit.experimental.theories.internal.ParameterizedAssertionError;
+import org.junit.internal.AssumptionViolatedException;
+import org.junit.runners.BlockJUnit4ClassRunner;
+import org.junit.runners.model.FrameworkMethod;
+import org.junit.runners.model.InitializationError;
+import org.junit.runners.model.Statement;
+import org.junit.runners.model.TestClass;
+
+public class Theories extends BlockJUnit4ClassRunner {
+ public Theories(Class<?> klass) throws InitializationError {
+ super(klass);
+ }
+
+ @Override
+ protected void collectInitializationErrors(List<Throwable> errors) {
+ super.collectInitializationErrors(errors);
+ validateDataPointFields(errors);
+ }
+
+ private void validateDataPointFields(List<Throwable> errors) {
+ Field[] fields= getTestClass().getJavaClass().getDeclaredFields();
+
+ for (Field each : fields)
+ if (each.getAnnotation(DataPoint.class) != null && !Modifier.isStatic(each.getModifiers()))
+ errors.add(new Error("DataPoint field " + each.getName() + " must be static"));
+ }
+
+ @Override
+ protected void validateConstructor(List<Throwable> errors) {
+ validateOnlyOneConstructor(errors);
+ }
+
+ @Override
+ protected void validateTestMethods(List<Throwable> errors) {
+ for (FrameworkMethod each : computeTestMethods())
+ if(each.getAnnotation(Theory.class) != null)
+ each.validatePublicVoid(false, errors);
+ else
+ each.validatePublicVoidNoArg(false, errors);
+ }
+
+ @Override
+ protected List<FrameworkMethod> computeTestMethods() {
+ List<FrameworkMethod> testMethods= super.computeTestMethods();
+ List<FrameworkMethod> theoryMethods= getTestClass().getAnnotatedMethods(Theory.class);
+ testMethods.removeAll(theoryMethods);
+ testMethods.addAll(theoryMethods);
+ return testMethods;
+ }
+
+ @Override
+ public Statement methodBlock(final FrameworkMethod method) {
+ return new TheoryAnchor(method, getTestClass());
+ }
+
+ public static class TheoryAnchor extends Statement {
+ private int successes= 0;
+
+ private FrameworkMethod fTestMethod;
+ private TestClass fTestClass;
+
+ private List<AssumptionViolatedException> fInvalidParameters= new ArrayList<AssumptionViolatedException>();
+
+ public TheoryAnchor(FrameworkMethod method, TestClass testClass) {
+ fTestMethod= method;
+ fTestClass= testClass;
+ }
+
+ private TestClass getTestClass() {
+ return fTestClass;
+ }
+
+ @Override
+ public void evaluate() throws Throwable {
+ runWithAssignment(Assignments.allUnassigned(
+ fTestMethod.getMethod(), getTestClass()));
+
+ if (successes == 0)
+ Assert
+ .fail("Never found parameters that satisfied method assumptions. Violated assumptions: "
+ + fInvalidParameters);
+ }
+
+ protected void runWithAssignment(Assignments parameterAssignment)
+ throws Throwable {
+ if (!parameterAssignment.isComplete()) {
+ runWithIncompleteAssignment(parameterAssignment);
+ } else {
+ runWithCompleteAssignment(parameterAssignment);
+ }
+ }
+
+ protected void runWithIncompleteAssignment(Assignments incomplete)
+ throws InstantiationException, IllegalAccessException,
+ Throwable {
+ for (PotentialAssignment source : incomplete
+ .potentialsForNextUnassigned()) {
+ runWithAssignment(incomplete.assignNext(source));
+ }
+ }
+
+ protected void runWithCompleteAssignment(final Assignments complete)
+ throws InstantiationException, IllegalAccessException,
+ InvocationTargetException, NoSuchMethodException, Throwable {
+ new BlockJUnit4ClassRunner(getTestClass().getJavaClass()) {
+ @Override
+ protected void collectInitializationErrors(
+ List<Throwable> errors) {
+ // do nothing
+ }
+
+ @Override
+ public Statement methodBlock(FrameworkMethod method) {
+ final Statement statement= super.methodBlock(method);
+ return new Statement() {
+ @Override
+ public void evaluate() throws Throwable {
+ try {
+ statement.evaluate();
+ handleDataPointSuccess();
+ } catch (AssumptionViolatedException e) {
+ handleAssumptionViolation(e);
+ } catch (Throwable e) {
+ reportParameterizedError(e, complete
+ .getArgumentStrings(nullsOk()));
+ }
+ }
+
+ };
+ }
+
+ @Override
+ protected Statement methodInvoker(FrameworkMethod method, Object test) {
+ return methodCompletesWithParameters(method, complete, test);
+ }
+
+ @Override
+ public Object createTest() throws Exception {
+ return getTestClass().getOnlyConstructor().newInstance(
+ complete.getConstructorArguments(nullsOk()));
+ }
+ }.methodBlock(fTestMethod).evaluate();
+ }
+
+ private Statement methodCompletesWithParameters(
+ final FrameworkMethod method, final Assignments complete, final Object freshInstance) {
+ return new Statement() {
+ @Override
+ public void evaluate() throws Throwable {
+ try {
+ final Object[] values= complete.getMethodArguments(
+ nullsOk());
+ method.invokeExplosively(freshInstance, values);
+ } catch (CouldNotGenerateValueException e) {
+ // ignore
+ }
+ }
+ };
+ }
+
+ protected void handleAssumptionViolation(AssumptionViolatedException e) {
+ fInvalidParameters.add(e);
+ }
+
+ protected void reportParameterizedError(Throwable e, Object... params)
+ throws Throwable {
+ if (params.length == 0)
+ throw e;
+ throw new ParameterizedAssertionError(e, fTestMethod.getName(),
+ params);
+ }
+
+ private boolean nullsOk() {
+ Theory annotation= fTestMethod.getMethod().getAnnotation(
+ Theory.class);
+ if (annotation == null)
+ return false;
+ return annotation.nullsAccepted();
+ }
+
+ protected void handleDataPointSuccess() {
+ successes++;
+ }
+ }
+}
diff --git a/src/main/java/org/junit/experimental/theories/Theory.java b/src/main/java/org/junit/experimental/theories/Theory.java
new file mode 100644
index 0000000..134fe9d
--- /dev/null
+++ b/src/main/java/org/junit/experimental/theories/Theory.java
@@ -0,0 +1,12 @@
+/**
+ *
+ */
+package org.junit.experimental.theories;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+@Retention(RetentionPolicy.RUNTIME)
+public @interface Theory {
+ boolean nullsAccepted() default true;
+} \ No newline at end of file
diff --git a/src/main/java/org/junit/experimental/theories/internal/AllMembersSupplier.java b/src/main/java/org/junit/experimental/theories/internal/AllMembersSupplier.java
new file mode 100644
index 0000000..615cc3e
--- /dev/null
+++ b/src/main/java/org/junit/experimental/theories/internal/AllMembersSupplier.java
@@ -0,0 +1,127 @@
+/**
+ *
+ */
+package org.junit.experimental.theories.internal;
+
+import java.lang.reflect.Array;
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.junit.experimental.theories.DataPoint;
+import org.junit.experimental.theories.DataPoints;
+import org.junit.experimental.theories.ParameterSignature;
+import org.junit.experimental.theories.ParameterSupplier;
+import org.junit.experimental.theories.PotentialAssignment;
+import org.junit.runners.model.FrameworkMethod;
+import org.junit.runners.model.TestClass;
+
+/**
+ * Supplies Theory parameters based on all public members of the target class.
+ */
+public class AllMembersSupplier extends ParameterSupplier {
+ static class MethodParameterValue extends PotentialAssignment {
+ private final FrameworkMethod fMethod;
+
+ private MethodParameterValue(FrameworkMethod dataPointMethod) {
+ fMethod= dataPointMethod;
+ }
+
+ @Override
+ public Object getValue() throws CouldNotGenerateValueException {
+ try {
+ return fMethod.invokeExplosively(null);
+ } catch (IllegalArgumentException e) {
+ throw new RuntimeException(
+ "unexpected: argument length is checked");
+ } catch (IllegalAccessException e) {
+ throw new RuntimeException(
+ "unexpected: getMethods returned an inaccessible method");
+ } catch (Throwable e) {
+ throw new CouldNotGenerateValueException();
+ // do nothing, just look for more values
+ }
+ }
+
+ @Override
+ public String getDescription() throws CouldNotGenerateValueException {
+ return fMethod.getName();
+ }
+ }
+
+ private final TestClass fClass;
+
+ /**
+ * Constructs a new supplier for {@code type}
+ */
+ public AllMembersSupplier(TestClass type) {
+ fClass= type;
+ }
+
+ @Override
+ public List<PotentialAssignment> getValueSources(ParameterSignature sig) {
+ List<PotentialAssignment> list= new ArrayList<PotentialAssignment>();
+
+ addFields(sig, list);
+ addSinglePointMethods(sig, list);
+ addMultiPointMethods(list);
+
+ return list;
+ }
+
+ private void addMultiPointMethods(List<PotentialAssignment> list) {
+ for (FrameworkMethod dataPointsMethod : fClass
+ .getAnnotatedMethods(DataPoints.class))
+ try {
+ addArrayValues(dataPointsMethod.getName(), list, dataPointsMethod.invokeExplosively(null));
+ } catch (Throwable e) {
+ // ignore and move on
+ }
+ }
+
+ @SuppressWarnings("deprecation")
+ private void addSinglePointMethods(ParameterSignature sig,
+ List<PotentialAssignment> list) {
+ for (FrameworkMethod dataPointMethod : fClass
+ .getAnnotatedMethods(DataPoint.class)) {
+ Class<?> type= sig.getType();
+ if ((dataPointMethod.producesType(type)))
+ list.add(new MethodParameterValue(dataPointMethod));
+ }
+ }
+
+ private void addFields(ParameterSignature sig,
+ List<PotentialAssignment> list) {
+ for (final Field field : fClass.getJavaClass().getFields()) {
+ if (Modifier.isStatic(field.getModifiers())) {
+ Class<?> type= field.getType();
+ if (sig.canAcceptArrayType(type)
+ && field.getAnnotation(DataPoints.class) != null) {
+ addArrayValues(field.getName(), list, getStaticFieldValue(field));
+ } else if (sig.canAcceptType(type)
+ && field.getAnnotation(DataPoint.class) != null) {
+ list.add(PotentialAssignment
+ .forValue(field.getName(), getStaticFieldValue(field)));
+ }
+ }
+ }
+ }
+
+ private void addArrayValues(String name, List<PotentialAssignment> list, Object array) {
+ for (int i= 0; i < Array.getLength(array); i++)
+ list.add(PotentialAssignment.forValue(name + "[" + i + "]", Array.get(array, i)));
+ }
+
+ private Object getStaticFieldValue(final Field field) {
+ try {
+ return field.get(null);
+ } catch (IllegalArgumentException e) {
+ throw new RuntimeException(
+ "unexpected: field from getClass doesn't exist on object");
+ } catch (IllegalAccessException e) {
+ throw new RuntimeException(
+ "unexpected: getFields returned an inaccessible field");
+ }
+ }
+} \ No newline at end of file
diff --git a/src/main/java/org/junit/experimental/theories/internal/Assignments.java b/src/main/java/org/junit/experimental/theories/internal/Assignments.java
new file mode 100644
index 0000000..bd94f00
--- /dev/null
+++ b/src/main/java/org/junit/experimental/theories/internal/Assignments.java
@@ -0,0 +1,133 @@
+/**
+ *
+ */
+package org.junit.experimental.theories.internal;
+
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.junit.experimental.theories.ParameterSignature;
+import org.junit.experimental.theories.ParameterSupplier;
+import org.junit.experimental.theories.ParametersSuppliedBy;
+import org.junit.experimental.theories.PotentialAssignment;
+import org.junit.experimental.theories.PotentialAssignment.CouldNotGenerateValueException;
+import org.junit.runners.model.TestClass;
+
+/**
+ * A potentially incomplete list of value assignments for a method's formal
+ * parameters
+ */
+public class Assignments {
+ private List<PotentialAssignment> fAssigned;
+
+ private final List<ParameterSignature> fUnassigned;
+
+ private final TestClass fClass;
+
+ private Assignments(List<PotentialAssignment> assigned,
+ List<ParameterSignature> unassigned, TestClass testClass) {
+ fUnassigned= unassigned;
+ fAssigned= assigned;
+ fClass= testClass;
+ }
+
+ /**
+ * Returns a new assignment list for {@code testMethod}, with no params
+ * assigned.
+ */
+ public static Assignments allUnassigned(Method testMethod,
+ TestClass testClass) throws Exception {
+ List<ParameterSignature> signatures;
+ signatures= ParameterSignature.signatures(testClass
+ .getOnlyConstructor());
+ signatures.addAll(ParameterSignature.signatures(testMethod));
+ return new Assignments(new ArrayList<PotentialAssignment>(),
+ signatures, testClass);
+ }
+
+ public boolean isComplete() {
+ return fUnassigned.size() == 0;
+ }
+
+ public ParameterSignature nextUnassigned() {
+ return fUnassigned.get(0);
+ }
+
+ public Assignments assignNext(PotentialAssignment source) {
+ List<PotentialAssignment> assigned= new ArrayList<PotentialAssignment>(
+ fAssigned);
+ assigned.add(source);
+
+ return new Assignments(assigned, fUnassigned.subList(1, fUnassigned
+ .size()), fClass);
+ }
+
+ public Object[] getActualValues(int start, int stop, boolean nullsOk)
+ throws CouldNotGenerateValueException {
+ Object[] values= new Object[stop - start];
+ for (int i= start; i < stop; i++) {
+ Object value= fAssigned.get(i).getValue();
+ if (value == null && !nullsOk)
+ throw new CouldNotGenerateValueException();
+ values[i - start]= value;
+ }
+ return values;
+ }
+
+ public List<PotentialAssignment> potentialsForNextUnassigned()
+ throws InstantiationException, IllegalAccessException {
+ ParameterSignature unassigned= nextUnassigned();
+ return getSupplier(unassigned).getValueSources(unassigned);
+ }
+
+ public ParameterSupplier getSupplier(ParameterSignature unassigned)
+ throws InstantiationException, IllegalAccessException {
+ ParameterSupplier supplier= getAnnotatedSupplier(unassigned);
+ if (supplier != null)
+ return supplier;
+
+ return new AllMembersSupplier(fClass);
+ }
+
+ public ParameterSupplier getAnnotatedSupplier(ParameterSignature unassigned)
+ throws InstantiationException, IllegalAccessException {
+ ParametersSuppliedBy annotation= unassigned
+ .findDeepAnnotation(ParametersSuppliedBy.class);
+ if (annotation == null)
+ return null;
+ return annotation.value().newInstance();
+ }
+
+ public Object[] getConstructorArguments(boolean nullsOk)
+ throws CouldNotGenerateValueException {
+ return getActualValues(0, getConstructorParameterCount(), nullsOk);
+ }
+
+ public Object[] getMethodArguments(boolean nullsOk)
+ throws CouldNotGenerateValueException {
+ return getActualValues(getConstructorParameterCount(),
+ fAssigned.size(), nullsOk);
+ }
+
+ public Object[] getAllArguments(boolean nullsOk)
+ throws CouldNotGenerateValueException {
+ return getActualValues(0, fAssigned.size(), nullsOk);
+ }
+
+ private int getConstructorParameterCount() {
+ List<ParameterSignature> signatures= ParameterSignature
+ .signatures(fClass.getOnlyConstructor());
+ int constructorParameterCount= signatures.size();
+ return constructorParameterCount;
+ }
+
+ public Object[] getArgumentStrings(boolean nullsOk)
+ throws CouldNotGenerateValueException {
+ Object[] values= new Object[fAssigned.size()];
+ for (int i= 0; i < values.length; i++) {
+ values[i]= fAssigned.get(i).getDescription();
+ }
+ return values;
+ }
+} \ No newline at end of file
diff --git a/src/main/java/org/junit/experimental/theories/internal/ParameterizedAssertionError.java b/src/main/java/org/junit/experimental/theories/internal/ParameterizedAssertionError.java
new file mode 100644
index 0000000..285bc3a
--- /dev/null
+++ b/src/main/java/org/junit/experimental/theories/internal/ParameterizedAssertionError.java
@@ -0,0 +1,49 @@
+/**
+ *
+ */
+package org.junit.experimental.theories.internal;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Iterator;
+
+
+public class ParameterizedAssertionError extends RuntimeException {
+ private static final long serialVersionUID = 1L;
+
+ public ParameterizedAssertionError(Throwable targetException,
+ String methodName, Object... params) {
+ super(String.format("%s(%s)", methodName, join(", ", params)),
+ targetException);
+ }
+
+ @Override public boolean equals(Object obj) {
+ return toString().equals(obj.toString());
+ }
+
+ public static String join(String delimiter, Object... params) {
+ return join(delimiter, Arrays.asList(params));
+ }
+
+ public static String join(String delimiter,
+ Collection<Object> values) {
+ StringBuffer buffer = new StringBuffer();
+ Iterator<Object> iter = values.iterator();
+ while (iter.hasNext()) {
+ Object next = iter.next();
+ buffer.append(stringValueOf(next));
+ if (iter.hasNext()) {
+ buffer.append(delimiter);
+ }
+ }
+ return buffer.toString();
+ }
+
+ private static String stringValueOf(Object next) {
+ try {
+ return String.valueOf(next);
+ } catch (Throwable e) {
+ return "[toString failed]";
+ }
+ }
+} \ No newline at end of file
diff --git a/src/main/java/org/junit/experimental/theories/suppliers/TestedOn.java b/src/main/java/org/junit/experimental/theories/suppliers/TestedOn.java
new file mode 100644
index 0000000..d6ede64
--- /dev/null
+++ b/src/main/java/org/junit/experimental/theories/suppliers/TestedOn.java
@@ -0,0 +1,13 @@
+package org.junit.experimental.theories.suppliers;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+import org.junit.experimental.theories.ParametersSuppliedBy;
+
+
+@ParametersSuppliedBy(TestedOnSupplier.class)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface TestedOn {
+ int[] ints();
+}
diff --git a/src/main/java/org/junit/experimental/theories/suppliers/TestedOnSupplier.java b/src/main/java/org/junit/experimental/theories/suppliers/TestedOnSupplier.java
new file mode 100644
index 0000000..f80298f
--- /dev/null
+++ b/src/main/java/org/junit/experimental/theories/suppliers/TestedOnSupplier.java
@@ -0,0 +1,23 @@
+package org.junit.experimental.theories.suppliers;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import org.junit.experimental.theories.ParameterSignature;
+import org.junit.experimental.theories.ParameterSupplier;
+import org.junit.experimental.theories.PotentialAssignment;
+
+
+
+public class TestedOnSupplier extends ParameterSupplier {
+ @Override public List<PotentialAssignment> getValueSources(ParameterSignature sig) {
+ List<PotentialAssignment> list = new ArrayList<PotentialAssignment>();
+ TestedOn testedOn = sig.getAnnotation(TestedOn.class);
+ int[] ints = testedOn.ints();
+ for (final int i : ints) {
+ list.add(PotentialAssignment.forValue(Arrays.asList(ints).toString(), i));
+ }
+ return list;
+ }
+}