diff options
198 files changed, 10544 insertions, 4454 deletions
@@ -1 +1,2 @@ bin +src/META-INF/MANIFEST.MF @@ -21,4 +21,8 @@ LOCAL_SRC_FILES := $(call all-java-files-under, src) LOCAL_MODULE := junit +LOCAL_MODULE_TAGS := optional + +LOCAL_STATIC_JAVA_LIBRARIES := hamcrest-host + include $(BUILD_HOST_JAVA_LIBRARY) diff --git a/README.android b/README.android index 704cf9e..c4194e4 100644 --- a/README.android +++ b/README.android @@ -1,4 +1,4 @@ -This is junit3.8.2 source, intended for host side use. +This is junit4.10 source, currently intended for host side use. + +Obtained from https://github.com/downloads/KentBeck/junit/junit-4.10-src.jar -Obtained from http://sourceforge.net/project/showfiles.php?group_id=15278&package_id=12472 without -modification.
\ No newline at end of file diff --git a/README.html b/README.html index df6f10f..42f29a6 100644 --- a/README.html +++ b/README.html @@ -1,736 +1,672 @@ -<!DOCTYPE html PUBLIC "-//w3c//dtd html 4.0 transitional//en"> +<!doctype html public "-//w3c//dtd html 4.0 transitional//en"> <html> <head> - <meta http-equiv="Content-Type" - content="text/html; charset=iso-8859-1"> - <meta name="GENERATOR" content="Microsoft FrontPage 4.0"> - <meta name="Author" content="Erich Gamma & Kent Beck"> - <title>JUnit 3.8</title> + <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1"> + <meta name="GENERATOR" content="Microsoft FrontPage 4.0"> + <meta name="Author" content="Erich Gamma, Kent Beck, and David Saff"> + <title>JUnit 4.6</title> </head> <body> + <h1> -<b><font color="#00cc00">J</font><font color="#ff0000">U</font><font - color="#000000">nit -3.8.2</font></b></h1> -<hr width="100%"> -<font color="#000000"><br> -11/11/2004<br> -</font> +<b><font color="#00CC00">J</font><font color="#FF0000">U</font><font color="#000000">nit +4.6</b></h1> +<br>Brought to you by <a href="http://www.threeriversinstitute.org">Kent Beck</a>, Erich +Gamma, and <a href="http://david.saff.net">David Saff</a>. +<br>FAQ edited by <a href="http://www.clarkware.com">Mike Clark</a>. Web mastering by Erik +Meade. +<br>(see also <a href="http://www.junit.org">JUnit.org</a>) + +<hr WIDTH="100%"> +<br>6 April 2009 +<p>JUnit is a simple framework to write repeatable tests. It is an instance +of the xUnit architecture for unit testing frameworks. <ul> - <li><font color="#000000"><a href="#Summary%20of">Summary of Changes</a></font></li> - <li> - <font color="#000000"><a href="#Contents">Contents</a></font></li> - <li> - <font color="#000000"><a href="#Installation">Installation</a></font></li> - <li> - <font color="#000000"><a href="#Getting">Getting Started</a></font></li> - <li> - <font color="#000000"><a href="#Documentation">Documentation</a></font></li> +<li> +<a href="#Summary of">Summary of Changes</a></li> + +<li> +<a href="#Contents">Contents</a></li> + +<li> +<a href="#Installation">Installation</a></li> + +<li> +<a href="#Getting">Getting Started</a></li> + +<li> +<a href="#Documentation">Documentation</a></li> +<li> +<a href="#Known Defects">Known Defects</a></li> </ul> -<h2> -<font><a name="Summary of"></a><font color="#000000">Summary of Changes -between 3.8.1 and 3.8.2</font></font></h2> -<font color="#000000">The changes between the versions are minimal and -the focus was on fixing the accumulated bug reports. The most -signifanct change is replacing the much-reviled string comparison -format with something easier to read and use.<br> -</font> -<ul> - <li>ComparisonFailure shows context. <br> - </li> - <ul> - <li>assertEquals("Mary had a little lamb", "Mary had the little -lamb") shows: expected:<Mary had [a] little lamb> but -was:<Mary had [the] little lamb><br> -Longer prefixes and suffixes are truncated to a fixed size (currently -20):</li> - <li>expected:<...st of the emergency [broadcasting] -system> but was:<...st of the emergency [locating] system><br> - </li> - </ul> - <li><font color="#000000">Running single tests. -junit.ui.TestRunner can be invoked with "-m</font> ClassName.testName" -to run a single test.</li> - <li>TestSuite(Class[]). -There is a new TestSuite constructor that takes an array of classes as -a parameter and returns a suite of suites, each of which is constructed -from a single class.</li> -</ul> -<h3><font><font color="#000000">Closed Bugs/Patches and Enhancment -Requests<br> -</font></font></h3> + +<a NAME="Summary of"> +<h2>Summary of Changes in version 4.6</h2> + +<h3>Max</h3> + +<p>JUnit now includes a new experimental Core, <code>MaxCore</code>. <code>MaxCore</code> +remembers the results of previous test runs in order to run new +tests out of order. <code>MaxCore</code> prefers new tests to old tests, fast +tests to slow tests, and recently failing tests to tests that last +failed long ago. There's currently not a standard UI for running +<code>MaxCore</code> included in JUnit, but there is a UI included in the JUnit +Max Eclipse plug-in at:</p> + +<p>http://www.junitmax.com/junitmax/subscribe.html</p> + +<p>Example:</p> + +<pre><code>public static class TwoUnEqualTests { + @Test + public void slow() throws InterruptedException { + Thread.sleep(100); + fail(); + } + + @Test + public void fast() { + fail(); + } +} + +@Test +public void rememberOldRuns() { + File maxFile = new File("history.max"); + MaxCore firstMax = MaxCore.storedLocally(maxFile); + firstMax.run(TwoUnEqualTests.class); + + MaxCore useHistory= MaxCore.storedLocally(maxFile); + List<Failure> failures= useHistory.run(TwoUnEqualTests.class) + .getFailures(); + assertEquals("fast", failures.get(0).getDescription().getMethodName()); + assertEquals("slow", failures.get(1).getDescription().getMethodName()); +} +</code></pre> + +<h3>Test scheduling strategies</h3> + +<p><code>JUnitCore</code> now includes an experimental method that allows you to +specify a model of the <code>Computer</code> that runs your tests. Currently, +the only built-in Computers are the default, serial runner, and two +runners provided in the <code>ParallelRunner</code> class: +<code>ParallelRunner.classes()</code>, which runs classes in parallel, and +<code>ParallelRunner.methods()</code>, which runs classes and methods in parallel.</p> + +<p>This feature is currently less stable than MaxCore, and may be +merged with MaxCore in some way in the future.</p> + +<p>Example:</p> + +<pre><code>public static class Example { + @Test public void one() throws InterruptedException { + Thread.sleep(1000); + } + @Test public void two() throws InterruptedException { + Thread.sleep(1000); + } +} + +@Test public void testsRunInParallel() { + long start= System.currentTimeMillis(); + Result result= JUnitCore.runClasses(ParallelComputer.methods(), + Example.class); + assertTrue(result.wasSuccessful()); + long end= System.currentTimeMillis(); + assertThat(end - start, betweenInclusive(1000, 1500)); +} +</code></pre> + +<h3>Comparing double arrays</h3> + +<p>Arrays of doubles can be compared, using a delta allowance for equality:</p> + +<pre><code>@Test +public void doubleArraysAreEqual() { + assertArrayEquals(new double[] {1.0, 2.0}, new double[] {1.0, 2.0}, 0.01); +} +</code></pre> + +<h3><code>Filter.matchDescription</code> API</h3> + +<p>Since 4.0, it has been possible to run a single method using the <code>Request.method</code> +API. In 4.6, the filter that implements this is exposed as <code>Filter.matchDescription</code>.</p> + +<h3>Documentation</h3> + <ul> - <li><a - href="http://sourceforge.net/tracker/index.php?func=detail&aid=698067&group_id=15278&atid=115278">assertEquals(float,float,delta) -fails on negative delta</a></li> - <li><a - href="http://sourceforge.net/tracker/index.php?func=detail&aid=609972&group_id=15278&atid=115278">'...' -in ComparisonFailure</a></li> - <li><a - href="http://sourceforge.net/tracker/index.php?func=detail&aid=461535&group_id=15278&atid=115278">Trouble -in teardown hides orig. probl.</a></li> - <li><a - href="http://sourceforge.net/tracker/index.php?func=detail&aid=609819&group_id=15278&atid=115278">NaN's -in assertEquals</a></li> - <li><a - href="http://sourceforge.net/tracker/index.php?func=detail&aid=620039&group_id=15278&atid=115278">BaseTestRunner.setPreference -static</a></li> - <li><a - href="http://sourceforge.net/tracker/index.php?func=detail&aid=658044&group_id=15278&atid=115278">failNotEquals() -should be protected</a></li> - <li><a - href="http://sourceforge.net/tracker/index.php?func=detail&aid=777097&group_id=15278&atid=115278">RFE: -make private methods protected</a></li> - <li><a - href="http://sourceforge.net/tracker/index.php?func=detail&aid=654507&group_id=15278&atid=365278">Printing -version number</a></li> - <li><a - href="http://sourceforge.net/tracker/index.php?func=detail&aid=993150&group_id=15278&atid=315278">Patch -to quell warnings in tiger</a></li> - <li><a - href="http://sourceforge.net/tracker/index.php?func=detail&aid=657593&group_id=15278&atid=315278">Enhanced -ComparisonFailure output</a></li> - <li><a - href="http://sourceforge.net/tracker/index.php?func=detail&aid=625016&group_id=15278&atid=315278">addt'l -TestSuite constructrs for Class[]</a></li> - <li><a - href="http://sourceforge.net/tracker/index.php?func=detail&aid=908467&group_id=15278&atid=315278">Run -one test method for junit</a></li> - <li><a - href="http://sourceforge.net/tracker/index.php?func=detail&aid=756480&group_id=15278&atid=315278">excluded.properties: -Add commons logging</a></li> +<li><p>A couple classes and packages that once had empty javadoc have been +doc'ed.</p></li> +<li><p>Added how to run JUnit from the command line to the cookbook.</p></li> +<li><p>junit-4.x.zip now contains build.xml</p></li> </ul> -<h2><font color="#000000">Summary of Changes between 3.8 and 3.8.1</font></h2> + +<h3>Bug fixes</h3> + <ul> - <font color="#000000"> <li>Backed out setting the testing Thread's -context class loader (see <a - href="https://sourceforge.net/tracker/index.php?func=detail&aid=598200&group_id=15278&atid=115278">JUnit -not setting ClassLoader</a>). It has caused problems in tests that -worked OK before. See the bug report for more details.</li> - <li>Fixes: - <ul> - <li><a - href="https://sourceforge.net/tracker/index.php?func=detail&aid=601326&group_id=15278&atid=115278">NPE -in ComparisonFailure</a></li> - <li><a - href="https://sourceforge.net/tracker/index.php?func=detail&aid=602948&group_id=15278&atid=115278">Swing -UI: NoSuchMethodError on JDK 1.3</a></li> - </ul> - </li> - </font> +<li>Fixed overly permissive @DataPoint processing (2191102)</li> +<li>Fixed bug in test counting after an ignored method (2106324)</li> </ul> -<h2> -<font color="#000000">Summary of Changes between 3.7 and 3.8</font></h2> -<h3> -<font color="#000000">Framework</font></h3> + +<h2>Summary of Changes in version 4.5</h2> + +<h3>Installation</h3> + <ul> - <li> - <font color="#000000">Made the string argument TestCase constructor -optional. You can now delete -constructors of the form "FooTestCase(String name) { super(name); }".</font></li> - <li> - <font color="#000000">Deleted deprecated assert(boolean) in favor -of assertTrue(boolean) and -assertFalse(boolean). To migrate to JUnit 3.8, rename calls to -assert(boolean) -to call assertTrue(boolean).</font></li> - <li> - <font color="#000000">Added assertFalse() to avoid the difficult of -reading the assertTrue(! -condition).</font></li> - <li> - <font color="#000000">Added assertNotSame(Object, Object).</font></li> - <li> - <font color="#000000">Deleted deprecated TestCase.name() in favor -of TestCase.getName().</font></li> - <li> - <font color="#000000">Deleted deprecated package junit.ui in favor -of junit.awtui.</font></li> +<li>We are releasing <code>junit-4.6.jar</code>, which contains all the classes +necessary to run JUnit, and <code>junit-dep-4.6.jar</code>, which leaves out +hamcrest classes, for developers who already use hamcrest outside of +JUnit.</li> </ul> -<h3> -<font color="#000000">Test Runner</font></h3> + +<h3>Basic JUnit operation</h3> + <ul> - <li> - <font color="#000000">When you compare two long strings with a -small delta embedded in the middle, it -is hard to spot the difference. In 3.8, when you call -assertEquals(String, -String), only the differences between the strings are displayed. The -common -prefix and suffix are replaced with "...".</font></li> - <li> - <font color="#000000">Added initial version of a TestRunListener -attached to TestRunners which -eventually will replace TestListeners attached to the TestResult.</font></li> - <li> - <font color="#000000">Filled in ActiveTestSuite constructors.</font></li> - <li> - <font color="#000000">Added these packages to the -excluded.properties:<font size="2"> - <ul> - <li>org.w3c.dom.*</li> - <li>org.xml.sax.*</li> - <li>net.jini.*</li> - </ul> - </font></font></li> - <li><font color="#000000">Extracted textual formatting of a -TestResult from junit.textui.TestRunner into ResultPrinter.</font></li> +<li><p>JUnitCore now more often exits with the correct exit code (0 for +success, 1 for failure)</p></li> +<li><p>Badly formed test classes (exceptions in constructors, classes +without tests, multiple constructors, Suite without @SuiteClasses) +produce more helpful error messages</p></li> +<li><p>Test classes whose only test methods are inherited from superclasses +now run.</p></li> +<li><p>Optimization to annotation processing can cut JUnit overhead by more than half +on large test classes, especially when using Theories. [Bug 1796847]</p></li> +<li><p>A failing assumption in a constructor ignores the class</p></li> +<li><p>Correct results when comparing the string "null" with potentially +null values. [Bug 1857283]</p></li> +<li><p>Annotating a class with <code>@RunWith(JUnit4.class)</code> will always invoke the +default JUnit 4 runner in the current version of JUnit. This default changed +from <code>JUnit4ClassRunner</code> in 4.4 to <code>BlockJUnit4ClassRunner</code> in 4.5 (see below), +and may change again.</p></li> </ul> -<h3><font color="#000000">Documentation</font></h3> + +<h3>Extension</h3> + <ul> - <font color="#000000"> <li>Much improved <a href="doc/faq/faq.htm">FAQ</a> -thanks to Mike Clark.</li> - </font> -</ul> -<h3><font color="#000000">Closed Bugs</font></h3> +<li><p><code>BlockJUnit4Runner</code> is a new implementation of the standard JUnit 4 +test class functionality. In contrast to <code>JUnit4ClassRunner</code> (the old +implementation):</p> + <ul> - <font color="#000000"> <li><a - href="https://sourceforge.net/tracker/index.php?func=detail&aid=583346&group_id=15278&atid=115278">Class -loader problem</a></li> - <li><a - href="https://sourceforge.net/tracker/index.php?func=detail&aid=551239&group_id=15278&atid=115278">Cookbook -Simple Test Case problems</a></li> - <li><a - href="https://sourceforge.net/tracker/index.php?func=detail&aid=539015&group_id=15278&atid=115278">License -not included in source</a></li> - <li><a - href="https://sourceforge.net/tracker/index.php?func=detail&aid=560081&group_id=15278&atid=115278">assert -is a keyword</a></li> - <li><a - href="https://sourceforge.net/tracker/index.php?func=detail&aid=572444&group_id=15278&atid=115278">javadoc -returns mysterious message</a></li> - <li><a - href="https://sourceforge.net/tracker/index.php?func=detail&aid=581251&group_id=15278&atid=115278">swingui -CounterPanel values disappear</a></li> - <li><a - href="https://sourceforge.net/tracker/index.php?func=detail&aid=574641&group_id=15278&atid=115278">TestCase -javadoc incorrect example</a></li> - <li><a - href="https://sourceforge.net/tracker/index.php?func=detail&aid=582784&group_id=15278&atid=115278">silly -cookbook error</a></li> - <li><a - href="https://sourceforge.net/tracker/index.php?func=detail&aid=583328&group_id=15278&atid=115278">junit -properties missfunction</a></li> - <li><a - href="https://sourceforge.net/tracker/index.php?func=detail&aid=595957&group_id=15278&atid=115278">Test.java -is not Serializable</a></li> - <li><a - href="https://sourceforge.net/tracker/index.php?func=detail&aid=598200&group_id=15278&atid=115278">JUnit -not setting ClassLoader`</a></li> - <li><a - href="https://sourceforge.net/tracker/index.php?func=detail&aid=592713&group_id=15278&atid=115278">NullPointerException -when loading suite</a></li> - <li><a - href="https://sourceforge.net/tracker/index.php?func=detail&aid=544683&group_id=15278&atid=115278">labels -for bug counts too small in Swing</a></li> - <li><a - href="https://sourceforge.net/tracker/index.php?func=detail&aid=537502&group_id=15278&atid=115278">Swing -TestRunner layout shifts</a></li> - <li><a - href="https://sourceforge.net/tracker/index.php?func=detail&aid=528868&group_id=15278&atid=115278">Exit -code problem for cygwin/w2k</a></li> - <li><a - href="https://sourceforge.net/tracker/index.php?func=detail&aid=529953&group_id=15278&atid=115278">Automatic -reload causes strange errors</a></li> - <li><a - href="https://sourceforge.net/tracker/index.php?func=detail&aid=532952&group_id=15278&atid=115278">TestRunner -fails with swing/awtui</a></li> - <li><a - href="https://sourceforge.net/tracker/index.php?func=detail&aid=533411&group_id=15278&atid=115278">CVS -version doesn't build on NetBSD</a></li> - <li><a - href="https://sourceforge.net/tracker/index.php?func=detail&aid=520210&group_id=15278&atid=115278">NullPointerException -JUnit sample w/ Ant</a></li> - <li><a - href="https://sourceforge.net/tracker/index.php?func=detail&aid=527511&group_id=15278&atid=115278">money -sample bug</a></li> - <li><a - href="https://sourceforge.net/tracker/index.php?func=detail&aid=491981&group_id=15278&atid=115278">incomplete -message from failNotSame()</a></li> - <li><a - href="https://sourceforge.net/tracker/index.php?func=detail&aid=471577&group_id=15278&atid=115278">Icons -on systems with 64 colors exceptio</a></li> - <li><a - href="https://sourceforge.net/tracker/index.php?func=detail&aid=470324&group_id=15278&atid=115278">1000+ -tests, swing gui doesn't display</a></li> - <li><a - href="https://sourceforge.net/tracker/index.php?func=detail&aid=442752&group_id=15278&atid=115278">test -selector included incorrect classes</a></li> - <li><a - href="https://sourceforge.net/tracker/index.php?func=detail&aid=430974&group_id=15278&atid=115278">No -UI update when re-run methods fail</a></li> - </font> +<li><p><code>BlockJUnit4Runner</code> has a much simpler implementation based on +Statements, allowing new operations to be inserted into the +appropriate point in the execution flow.</p></li> +<li><p><code>BlockJUnit4Runner</code> is published, and extension and reuse are +encouraged, whereas <code>JUnit4ClassRunner</code> was in an internal package, +and is now deprecated.</p></li> +</ul></li> +<li><p><code>ParentRunner</code> is a base class for runners that iterate over +a list of "children", each an object representing a test or suite to run. +<code>ParentRunner</code> provides filtering, sorting, <code>@BeforeClass</code>, <code>@AfterClass</code>, +and method validation to subclasses.</p></li> +<li><p><code>TestClass</code> wraps a class to be run, providing efficient, repeated access +to all methods with a given annotation.</p></li> +<li><p>The new <code>RunnerBuilder</code> API allows extending the behavior of +Suite-like custom runners.</p></li> +<li><p><code>AssumptionViolatedException.toString()</code> is more informative</p></li> </ul> -<h2> -<font color="#000000">Summary of Changes between 3.6 and 3.7</font></h2> -<h3> -<font color="#000000">GUI</font></h3> + +<h3>Extra Runners</h3> + <ul> - <li> - <font color="#000000">Eliminated warning when re-running tests when -class loading checkbox is -unchecked. There are legitimate reasons for doing this, so a warning -didn't -make much sense, and it was too obtrusive.</font></li> - <li> - <font color="#000000">Stopped reloading classes when running in -VisualAge for Java.</font></li> - <li> - <font color="#000000">Print total number of tests as well as number -of tests run so far (Swing -only).</font></li> +<li><p><code>Parameterized.eachOne()</code> has been removed</p></li> +<li><p>New runner <code>Enclosed</code> runs all static inner classes of an outer class.</p></li> </ul> -<h3> -<font color="#000000">Framework</font></h3> + +<h3>Theories</h3> + <ul> - <li> - <font color="#000000">Introduced Assert.assertTrue(boolean) and -assertTrue(String, boolean) deprecated -assert(boolean) and assert(String, boolean) in preparation for the -assert -keyword in Java 1.4. We plan to support native assertions when they are -publicly available. You can either move to assertTrue() or wait for 1.4 -and delete parentheses as the syntax is e.g. "assert 2 == 3".</font></li> - <li> - <font color="#000000">Added accessors for TestCase.fName and -TestSuite.fName.</font></li> - <li> - <font color="#000000">Added a no argument TestCase constructor to -support serialization.</font></li> - <li> - <font color="#000000">Improved warnings when constructing -TestSuites.</font></li> +<li><p><code>@Before</code> and <code>@After</code> methods are run before and after each set of attempted parameters +on a Theory, and each set of parameters is run on a new instance of the test class.</p></li> +<li><p>Exposed API's <code>ParameterSignature.getType()</code> and <code>ParameterSignature.getAnnotations()</code></p></li> +<li><p>An array of data points can be introduced by a field or method +marked with the new annotation <code>@DataPoints</code></p></li> +<li><p>The Theories custom runner has been refactored to make it faster and +easier to extend</p></li> </ul> -<h3> -<font color="#000000">Text Runner</font></h3> + +<h3>Development</h3> + <ul> - <li> - <font color="#000000">Made doRun() public so clients can create a -text runner with a specified -output stream and then run tests.</font></li> +<li><p>Source has been split into directories <code>src/main/java</code> and +<code>src/test/java</code>, making it easier to exclude tests from builds, and +making JUnit more maven-friendly</p></li> +<li><p>Test classes in <code>org.junit.tests</code> have been organized into +subpackages, hopefully making finding tests easier.</p></li> +<li><p><code>ResultMatchers</code> has more informative descriptions.</p></li> +<li><p><code>TestSystem</code> allows testing return codes and other system-level interactions.</p></li> </ul> -<h3> -<font color="#000000">Fixed Bugs (SourceForge Bug Tracker Ids)</font></h3> -<font color="#000000"> [420315] No trace when fail -with message... -<br> - [419375] reload warning lags -<br> - [418849] Classloader warning too obtrusive -<br> - [417978] constructor stack trace, please -<br> - [415103] Reload checkbox should be ignored in VAJ -<br> - [414954] error reporting when invoking suite() -<br> - [407296] Make doRun() public -<br> - [227578] rmi callbacks fail since TestCase has no -noArg constructor -<br> - [422603] Decorated decorators bug -</font> -<h2><font color="#000000">Summary of Changes between 3.5 and 3.6</font></h2> -<h3> -<font color="#000000">TestRunner</font></h3> + +<h2>Summary of Changes in version 4.4</h2> + +<p>JUnit is designed to efficiently capture developers' intentions about +their code, and quickly check their code matches those intentions. +Over the last year, we've been talking about what things developers +would like to say about their code that have been difficult in the +past, and how we can make them easier.</p> + +<h3>assertThat</h3> + +<p>Two years ago, Joe Walnes built a <a href="http://joe.truemesh.com/blog/000511.html">new assertion mechanism</a> on top of what was +then <a href="http://www.jmock.org/download.html">JMock 1</a>. The method name was <code>assertThat</code>, and the syntax looked like this:</p> + +<pre><code>assertThat(x, is(3)); +assertThat(x, is(not(4))); +assertThat(responseString, either(containsString("color")).or(containsString("colour"))); +assertThat(myList, hasItem("3")); +</code></pre> + +<p>More generally:</p> + +<pre><code>assertThat([value], [matcher statement]); +</code></pre> + +<p>Advantages of this assertion syntax include:</p> + <ul> - <li> - <font color="#000000">The UI test runners provide a check box to -enable/disable the custom class -loader. The user is warned when running a second test with the non -loading -class loader.</font></li> - <li> - <font color="#000000">Renames to address file name length -limitation on MacOS:</font></li> - <ul> - <li> - <font color="#000000">LoadingClassPathTestCollector -> -LoadingTestCollector</font></li> - <li> - <font color="#000000">SimpleClassPathTestCollector -> -SimpleTestCollector</font></li> - </ul> +<li><p>More readable and typeable: this syntax allows you to think in terms of subject, verb, object +(assert "x is 3") rathern than <code>assertEquals</code>, which uses verb, object, subject (assert "equals 3 x")</p></li> +<li><p>Combinations: any matcher statement <code>s</code> can be negated (<code>not(s)</code>), combined (<code>either(s).or(t)</code>), +mapped to a collection (<code>each(s)</code>), or used in custom combinations (<code>afterFiveSeconds(s)</code>)</p></li> +<li><p>Readable failure messages. Compare</p> + +<pre><code>assertTrue(responseString.contains("color") || responseString.contains("colour")); +// ==> failure message: +// java.lang.AssertionError: + + +assertThat(responseString, anyOf(containsString("color"), containsString("colour"))); +// ==> failure message: +// java.lang.AssertionError: +// Expected: (a string containing "color" or a string containing "colour") +// got: "Please choose a font" +</code></pre></li> +<li><p>Custom Matchers. By implementing the <code>Matcher</code> interface yourself, you can get all of the +above benefits for your own custom assertions.</p></li> +<li><p>For a more thorough description of these points, see <a href="http://joe.truemesh.com/blog/000511.html">Joe Walnes's +original post</a>.:</p></li> </ul> -<h3> -<font color="#000000">Framework</font></h3> + +<p>We have decided to include this API directly in JUnit. +It's an extensible and readable syntax, and because it enables +new features, like <a href="#assumptions">assumptions</a> and <a href="#theories">theories</a>.</p> + +<p>Some notes:</p> + <ul> - <li> - <font color="#000000">Added TestSuite.getName()</font></li> +<li>The old assert methods are never, ever, going away. <br /> +Developers may continue using the old <code>assertEquals</code>, <code>assertTrue</code>, and +so on.</li> +<li><p>The second parameter of an <code>assertThat</code> statement is a <code>Matcher</code>. +We include the Matchers we want as static imports, like this:</p> + +<pre><code>import static org.hamcrest.CoreMatchers.is; +</code></pre> + +<p>or:</p> + +<pre><code>import static org.hamcrest.CoreMatchers.*; +</code></pre></li> +<li><p>Manually importing <code>Matcher</code> methods can be frustrating. [Eclipse +3.3][] includes the ability to +define +"Favorite" classes to import static methods from, which makes it easier +(Search for "Favorites" in the Preferences dialog). +We expect that support for static imports will improve in all Java IDEs in the future.</p></li> +<li><p>To allow compatibility with a wide variety of possible matchers, +we have decided to include the classes from hamcrest-core, +from the <a href="http://code.google.com/p/hamcrest/">Hamcrest</a> project. This is the first time that +third-party classes have been included in JUnit. </p></li> +<li><p>To allow developers to maintain full control of the classpath contents, the JUnit distribution also provides an unbundled junit-dep jar, +ie without hamcrest-core classes included. This is intended for situations when using other libraries that also depend on hamcrest-core, to +avoid classloading conflicts or issues. Developers using junit-dep should ensure a compatible version of hamcrest-core jar (ie 1.1+) is present in the classpath.</p></li> +<li><p>JUnit currently ships with a few matchers, defined in +<code>org.hamcrest.CoreMatchers</code> and <code>org.junit.matchers.JUnitMatchers</code>. <br /> +To use many, many more, consider downloading the <a href="http://hamcrest.googlecode.com/files/hamcrest-all-1.1.jar">full hamcrest package</a>.</p></li> +<li><p>JUnit contains special support for comparing string and array +values, giving specific information on how they differ. This is not +yet available using the <code>assertThat</code> syntax, but we hope to bring +the two assert methods into closer alignment in future releases.</p></li> </ul> -<h3> -<font color="#000000">Builds</font></h3> + +<h3>assumeThat</h3> + +<p><a name="assumptions" /> +Ideally, the developer writing a test has control of all of the forces that might cause a test to fail. +If this isn't immediately possible, making dependencies explicit can often improve a design. <br /> +For example, if a test fails when run in a different locale than the developer intended, +it can be fixed by explicitly passing a locale to the domain code.</p> + +<p>However, sometimes this is not desirable or possible. <br /> +It's good to be able to run a test against the code as it is currently written, +implicit assumptions and all, or to write a test that exposes a known bug. +For these situations, JUnit now includes the ability to express "assumptions":</p> + +<pre><code>import static org.junit.Assume.* + +@Test public void filenameIncludesUsername() { + assumeThat(File.separatorChar, is('/')); + assertThat(new User("optimus").configFileName(), is("configfiles/optimus.cfg")); +} + +@Test public void correctBehaviorWhenFilenameIsNull() { + assumeTrue(bugFixed("13356")); // bugFixed is not included in JUnit + assertThat(parse(null), is(new NullDocument())); +} +</code></pre> + +<p>With this beta release, a failed assumption will lead to the test being marked as passing, +regardless of what the code below the assumption may assert. +In the future, this may change, and a failed assumption may lead to the test being ignored: +however, third-party runners do not currently allow this option.</p> + +<p>We have included <code>assumeTrue</code> for convenience, but thanks to the +inclusion of Hamcrest, we do not need to create <code>assumeEquals</code>, +<code>assumeSame</code>, and other analogues to the <code>assert*</code> methods. All of +those functionalities are subsumed in assumeThat, with the appropriate +matcher.</p> + +<p>A failing assumption in a <code>@Before</code> or <code>@BeforeClass</code> method will have the same effect +as a failing assumption in each <code>@Test</code> method of the class.</p> + +<h3>Theories</h3> + +<p><a name="theories" /> +More flexible and expressive assertions, combined with the ability to +state assumptions clearly, lead to a new kind of statement of intent, +which we call a "Theory". A test captures the intended behavior in +one particular scenario. A theory allows a developer to be +as precise as desired about the behavior of the code in possibly +infinite numbers of possible scenarios. For example:</p> + +<pre><code>@RunWith(Theories.class) +public class UserTest { + @DataPoint public static String GOOD_USERNAME = "optimus"; + @DataPoint public static String USERNAME_WITH_SLASH = "optimus/prime"; + + @Theory public void filenameIncludesUsername(String username) { + assumeThat(username, not(containsString("/"))); + assertThat(new User(username).configFileName(), containsString(username)); + } +} +</code></pre> + +<p>This makes it clear that the user's filename should be included in the +config file name, only if it doesn't contain a slash. Another test +or theory might define what happens when a username does contain a slash.</p> + +<p><code>UserTest</code> will attempt to run <code>filenameIncludesUsername</code> on +every compatible <code>DataPoint</code> defined in the class. If any of the +assumptions fail, the data point is silently ignored. If all of the +assumptions pass, but an assertion fails, the test fails.</p> + +<p>The support for Theories has been absorbed from the <a href="http://popper.tigris.org">Popper</a> +project, and <a href="http://popper.tigris.org/tutorial.html">more complete documentation</a> can be found +there.</p> + +<p>Defining general statements in this way can jog the developer's memory +about other potential data points and tests, also allows <a href="http://www.junitfactory.org">automated +tools</a> to <a href="http://shareandenjoy.saff.net/2007/04/popper-and-junitfactory.html">search</a> for new, unexpected data +points that expose bugs.</p> + +<h3>Other changes</h3> + +<p>This release contains other bug fixes and new features. Among them:</p> + <ul> - <li> - <font color="#000000">Updated the build script for Ant 1.3.</font></li> +<li><p>Annotated descriptions</p> + +<p>Runner UIs, Filters, and Sorters operate on Descriptions of test +methods and test classes. These Descriptions now include the +annotations on the original Java source element, allowing for richer +display of test results, and easier development of annotation-based +filters.</p></li> +<li><p>Bug fix (1715326): assertEquals now compares all Numbers using their +native implementation of <code>equals</code>. This assertion, which passed in +4.3, will now fail:</p> + +<p>assertEquals(new Integer(1), new Long(1));</p> + +<p>Non-integer Numbers (Floats, Doubles, BigDecimals, etc), +which were compared incorrectly in 4.3, are now fixed.</p></li> +<li><p><code>assertEquals(long, long)</code> and <code>assertEquals(double, double)</code> have +been re-introduced to the <code>Assert</code> class, to take advantage of +Java's native widening conversions. Therefore, this still passes:</p> + +<p>assertEquals(1, 1L);</p></li> +<li><p>The default runner for JUnit 4 test classes has been refactored. +The old version was named <code>TestClassRunner</code>, and the new is named +<code>JUnit4ClassRunner</code>. Likewise, <code>OldTestClassRunner</code> is now +<code>JUnit3ClassRunner</code>. The new design allows variations in running +individual test classes to be expressed with fewer custom classes. +For a good example, see the source to +<code>org.junit.experimental.theories.Theories</code>.</p></li> +<li><p>The rules for determining which runner is applied by default to a +test class have been simplified:</p> + +<ol> +<li><p>If the class has a <code>@RunWith</code> annotation, the annotated runner +class is used.</p></li> +<li><p>If the class can be run with the JUnit 3 test runner (it +subclasses <code>TestCase</code>, or contains a <code>public static Test suite()</code> +method), JUnit38ClassRunner is used.</p></li> +<li><p>Otherwise, JUnit4ClassRunner is used.</p></li> +</ol> + +<p>This default guess can always be overridden by an explicit +<code>@RunWith(JUnit4ClassRunner.class)</code> or +<code>@RunWith(JUnit38ClassRunner.class)</code> annotation.</p> + +<p>The old class names <code>TestClassRunner</code> and <code>OldTestClassRunner</code> +remain as deprecated.</p></li> +<li><p>Bug fix (1739095): Filters and Sorters work correctly on test +classes that contain a <code>suite</code> method like:</p> + +<p>public static junit.framework.Test suite() { + return new JUnit4TestAdapter(MyTest.class); +}</p></li> +<li><p>Bug fix (1745048): @After methods are now correctly called +after a test method times out.</p></li> </ul> -<h3> -<font color="#000000">Fixed Bugs (SourceForge Bug Tracker Ids)</font></h3> -<blockquote><font color="#000000">[ #229753 ] assertEquals on NaN and -Infinity does not work -correctly - <br> -[ #229287 ] Class Name too long "SimpleClassPathTestCollector" - <br> -[ #229609 ] Stack Filtering missing in textui.TesRunner - <br> -[ #229870 ] Clicking on ... after tests failed gives NPE - <br> -[ #229974 ] Incorrect icon shown for first element in Swing GUI - <br> -[ #230581 ] swingui.TestTreeModel: results of decorated testcases... - <br> -[ #230971 ] Make junit.extensions.TestDecorator.getTest() public - <br> -[ #231569 ] DocBug: JUnit Test Infected: Programmers Love Writing Tests - <br> -[ #232645 ] BaseTestRunner.getTest loses exception information - <br> -[ #233094 ] TestSuite masks exceptions - <br> -[ #410967 ] No icon provided for first test - <br> -[ #230745 ] ClassPathTestCollector sometimes lists classes in duplicate</font></blockquote> -<h3> -<font color="#000000">Documentation</font></h3> + +<h2> +<a NAME="Summary of"></a>Summary of Changes in version 4.3.1</h2> +<p> <ul> - <li> - <font color="#000000">Added documentation about the <a - href="doc/JUnitProperties.html">properties</a> -supported by TestRunners.</font></li> - <li> - <font color="#000000">Updated the FAQ</font></li> +<li>Bug fix: 4.3 introduced a +<a href="https://sourceforge.net/tracker/?func=detail&atid=115278&aid=1684562&group_id=15278">bug</a> +that caused a NullPointerException +when comparing a null reference to a non-null reference in <tt>assertEquals</tt>. +This has been fixed. +<li>Bug fix: The binary jar for 4.3 <a href="https://sourceforge.net/tracker/?func=detail&atid=115278&aid=1686931&group_id=15278">accidentally</a> included the tests and sample code, +which are now removed for a smaller download, but, as always, available from the +full zip. </ul> +</p> + <h2> -<font color="#000000">Summary of Changes between 3.4 and 3.5</font></h2> -<h3> -<font color="#000000">Framework</font></h3> +<a NAME="Summary of"></a>Summary of Changes with version 4.3</h2> +<p> <ul> - <li> - <font color="#000000">Added TestSuite.addTestSuite(Class testClass)</font></li> - <font color="#000000"><br> -This method allows to create a TestSuite with a class containing test -cases directly. - <br> -Instead of writing <b>suite.addTest(new TestSuite(AssertTest.class)) - </b>you -can now write <b>suite.addTestSuite(AssertTest.class)</b>; - <li>Added assertEquals methods for all primitive types: float, -boolean, byte, -char, int, short</li> - <li> -The signature of TestListeners.addFailure(Test test, Throwable t)</li> - <br> -was changed to addFailure(Test test, AssertionFailedError t)</font> -</ul> -<h3> -<font color="#000000">TestRunner</font></h3> +<li>Changes in array equality. Using <tt>assertEquals</tt> to compare array contents is now deprecated. +In the future, <tt>assertEquals</tt> will revert to its pre-4.0 meaning of comparing objects based on +Java's <tt>Object.equals</tt> semantics. To compare array contents, use the new, more reliable +<tt>Assert.assertArrayEquals</tt> methods. +<li>The <tt>@Ignore</tt> annotation can now be applied to classes, to ignore the entire class, instead of +individual methods. +<li>Originally, developers who wanted to use a static <tt>suite()</tt> method from JUnit 3.x with a JUnit 4.x +runner had to annotate the class with <tt>@RunWith(AllTests.class)</tt>. In the common case, this requirement +has been removed. However, when such a class is wrapped with a JUnit4TestAdapter (which we believe is rare), the +results may not be as expected. +<li>Improved error messages for array comparison("arrays first differed at element [1][0]") +<li>Bug fix: Inaccessible base class is caught at test construction time. +<li>Bug fix: Circular suites are caught at test construction time. +<li>Bug fix: Test constructors that throw exceptions are reported correctly. +<li><b>For committers and extenders</b> <ul> - <li> - <font color="#000000">The Swing TestRunner provides an experimental -feature to browse test classes. -There is an additional browse ("...") button besides the suite combo. -It -shows a simple dialog to select a test class from a list. Different -strategies -to locate Test classes are supported and you can plug-in your own -strategy. -This allows to leverage functionality provided by an extension API of -an -integrated development environment (IDE). To define a custom test -collector -you 1) implement the <b>junit.runner.TestCollector </b>interface and -2) -add an entry to the <b>junit.properties</b> file with the key <b>TestCollectorClass</b> -and the name of your TestCollector implementation class as the key:</font></li> - <font color="#000000"><br> - -TestCollectorClass=junit.swingui.LoadingClassPathTestCollector - <br> -This class has to be installed on the class path. - <br> -JUnit provides two different TestCollector implementations: - </font> - <ul> - <li> - <font color="#000000">simple -(junit.runner.SimpleClassPathTestCollector) - considers all classes -on the class path on the file system that contain "Test" in their name. -Classes in JARs are not considered.</font></li> - <li> - <font color="#000000">loading -(junit.runner.LoadingClassPathTestCollector) - loads all classes -on the class path and tests whether the class is assignable from Test -or -has a static <b>suite</b> method.</font></li> - </ul> - <font color="#000000">By default the simple the test collector is -used. The loading collector -is more precise but much slower than the simple one. The loading -collector -doesn't scale up when many classes are available on the class path. - <br> - <b><font color="#ff0000">Notice</font></b>: that both TestCollectors -assume that the class files reside are kept in the file system. This -isn't -case in VA/Java and they will not work there. A custom TestCollector is -required for VA/Java. - <li>The Swing TestRunner now provides an additional test result view -that shows -all tests of the executed test suite as a tree. The view shows the -success -status for each test. The view is shown as an additional tab in the -TestRunner -window. In previous versions of JUnit this view was shown in a separate -window.</li> - <li> -The failure panels in the Swing and AWT TestRunners filter the -exception -stack trace so that only non-framework stack frames are shown.</li> - <li> -There is support to plug-in a custom failure panel that provides -additional -functionality like navigating from a failure to the source. To do so -you -implement the <b>junit.runner.FailureDetailView</b> interface and -register -the implementation class in the junit.properties file under the key <b>FailureViewClass</b>, -for example</li> - <br> - -FailureViewClass=MyFailureViewClassName. - <li>The Swing and AWT TestRunners now understand an additional -command line -argument "-noloading". When this argument is set then the standard -system -class loader is used to load classes. This is an alternative to setting -the <b>loading</b> property to false in the junit.properties file.</li> - <li> -Swing TestRunner - the maximum test history length shown in the suite -combo -can be defined in the junit.properties file with the key <b>maxhistory</b>.</li> - <li> -BaseTestRunner.<b>getLoader</b>() is no longer a static method and can -now be overridden in subclasses.</li> - <li> -BaseTestRunner removed dependency on JDK 1.2.</li> - <li> -Swing TestRunner - fixed the problem that a suite name was sometimes -duplicated -in the history combo.</li> - <li> -Swing TestRunner - the Run button is now the default button.</li> - <li> -Output string truncation can now be controlled by adding the <b>maxmessage</b> -key with the desired maximum length to the junit.properties file. -Setting -maxmessage to -1 means no output truncation.</li> - <li> -The Text TestRunner now shows the summary at the very end so that you -don't -have to scroll back.</li> - </font> +<li>Sources now are in a separate "src" directory (this means a big break in the CVS history) +<li>Improved documentation in <tt>Request</tt>, <tt>RunWith</tt> </ul> -<h3> -<font color="#000000">Tests</font></h3> -<ul> - <li> - <font color="#000000">TextRunnerTest now only depends on a nonzero -status to indicate abnormal -termination.</font></li> - <li> - <font color="#000000">TextRunnerTest now also passes on JDK 1.1.*. -It uses the -classpath command -line argument instead of -cp.</font></li> </ul> -<h3> -<font color="#000000">Documentation</font></h3> +</p> + +<h2> +<a NAME="Summary of"></a>Summary of Changes with version 4.2</h2> +<p> <ul> - <li> - <font color="#000000">Add an FAQ entry about what to do when the -junit tests provided with the -distribution can't be found.</font></li> +<li>Bug fix: Inaccessible base class is caught at test construction time. +<li>Bug fix: Circular suites are caught at test construction time. +<li>Improved error messages for array comparison("arrays first differed at element [1][0]") +<li>Test constructors that throw exceptions are reported correctly. </ul> +</p> + + <h2> -<font color="#000000">Older Change Notes</font></h2> -<blockquote> - <li><font color="#000000">Changes between <a - href="doc/changes34.html">2.1 and 3.4</a></font></li> - <li> - <font color="#000000">Changes between <a href="doc/changes21.html">1.0 -and 2.1</a></font></li> -</blockquote> +<a NAME="Summary of"></a>Summary of Changes with version 4.1</h2> +<p> +<ul> +<li>Bug fix: listeners now get a correct test running time, rather than always being told 0 secs. +<li>The @RunWith annotation is now inherited by subclasses: +all subclasses of an abstract test class will be run by the same runner. +<li>The build script fails if the JUnit unit tests fail +<li>The faq has been updated +<li>Javadoc has been improved, with more internal links, and package descriptions added (Thanks, Matthias Schmidt!) +<li>An acknowledgements.txt file has been created to credit outside contributions +<li>The <tt>Enclosed</tt> runner, which runs all of the static inner classes of a given class, has been added +to <tt>org.junit.runners</tt>. +</ul> +</p> + +<h2>Summary of Changes with version 4.0</h2> +<p> +The architecture of JUnit 4.0 is a substantial departure from that of earlier releases. +Instead of +tagging test classes by subclassing junit.framework.TestCase and tagging test methods by +starting their name with "test", you now tag test methods with the @Test annotation. +</p> + + <h2> -<font color="#000000"><a name="Contents"></a>Contents of the Release</font></h2> -<table cellspacing="0" cellpadding="0"> - <tbody> - <tr> - <td><tt>README.html </tt></td> - <td>this file</td> - </tr> - <tr> - <td><tt>junit.jar</tt></td> - <td>a jar file with the JUnit framework and tools </td> - </tr> - <tr> - <td>src.jar</td> - <td>a jar file with the source code of the junit framework</td> - </tr> - <tr> - <td><tt>junit</tt></td> - <td>the source code of the JUnit samples</td> - </tr> - <tr> - <td><tt> samples</tt></td> - <td>sample test cases</td> - </tr> - <tr> - <td><tt> tests</tt></td> - <td>test cases for JUnit itself</td> - </tr> - <tr> - <td><tt>javadoc</tt></td> - <td>javadoc generated documentation</td> - </tr> - <tr> - <td><tt>doc</tt></td> - <td>documentation and articles</td> - </tr> - </tbody> +<a NAME="Contents"></a>Contents of the Release</h2> + +<table CELLSPACING=0 CELLPADDING=0 > +<tr> +<td><tt>README.html </tt></td> + +<td>this file</td> +</tr> + +<tr> +<td><tt>junit-4.6.jar</tt></td> + +<td>a jar file with the JUnit framework, bundled with the hamcrest-core-1.1 dependency.</td> +</tr> + +<tr> +<td><tt>junit-dep-4.6.jar</tt></td> + +<td>a jar file with the JUnit framework, unbundled from any external dependencies. +Choosing to use this jar developers will need to also provide in the classpath a compatible version of external dependencies (ie hamcrest-core-1.1+)</td> +</tr> + +<tr> +<td><tt>junit-4.6-src.jar</tt></td> + +<td>a jar file with the source code of the JUnit framework</td> +</tr> + +<tr> +<td><tt>org/junit</tt></td> + +<td>the source code of the basic JUnit annotations and classes</td> +</tr> + +<tr> +<td><tt> samples</tt></td> + +<td>sample test cases</td> +</tr> + +<tr> +<td><tt> tests</tt></td> + +<td>test cases for JUnit itself</td> +</tr> + +<tr> +<td><tt>javadoc</tt></td> + +<td>javadoc generated documentation</td> +</tr> + +<tr> +<td><tt>doc</tt></td> + +<td>documentation and articles</td> +</tr> </table> + <h2> -<font color="#000000"><a name="Installation"></a>Installation</font></h2> -<font color="#000000">Below are the installation steps for installing -JUnit: -</font> +<a NAME="Installation"></a>Installation</h2> +Below are the installation steps for installing JUnit: <ol> - <li> - <font color="#000000">unzip the junit.zip file</font></li> - <li> - <font color="#000000">add<i> </i><b>junit.jar</b> to the -CLASSPATH. For example: <tt>set -classpath=%classpath%;INSTALL_DIR\junit3\junit.jar</tt></font></li> - <li> - <font color="#000000">test the installation by using either the -batch or the graphical TestRunner -tool to run the tests that come with this release. All the tests should -pass OK.</font></li> - <font color="#000000"><br> - <b><font color="#ff0000">Notice</font></b>: that the tests are not -contained in the junit.jar but in the installation directory directly. -Therefore make sure that the installation directory is on the class -path - </font> - <ul> - <li> - <font color="#000000">for the batch TestRunner type:</font></li> - <font color="#000000"><br> - <tt> java junit.textui.TestRunner -junit.samples.AllTests</tt> - <li>for the graphical TestRunner type:</li> - <br> - <tt> java junit.awtui.TestRunner -junit.samples.AllTests</tt> - <li>for the Swing based graphical TestRunner type:</li> - <br> - <tt> java junit.swingui.TestRunner -junit.samples.AllTests</tt></font> - </ul> +<li> +unzip the junit4.6.zip file</li> + +<li> +add<i> </i><b>junit-4.6.jar</b> to the CLASSPATH. For example: +<tt> set classpath=%classpath%;INSTALL_DIR\junit-4.6.jar;INSTALL_DIR</tt></li> + +<li> +test the installation by running <tt>java org.junit.runner.JUnitCore org.junit.tests.AllTests</tt></li> + +<br><b><font color="#FF0000">Notice</font></b>: that the tests are not +contained in the junit-4.6.jar but in the installation directory directly. +Therefore make sure that the installation directory is on the class path </ol> -<font color="#000000"><b><font color="#ff0000">Important</font></b>: -don't install the junit.jar +<b><font color="#FF0000">Important</font></b>: don't install junit-4.6.jar into the extension directory of your JDK installation. If you do so the test class on the files system will not be found. -</font> -<h2><font color="#000000"><a name="Getting"></a>Getting Started</font></h2> -<font color="#000000">To get started with unit testing and JUnit read -the Java Report article: -<a href="doc/testinfected/testing.htm">Test -Infected - Programmers Love Writing Tests</a>. -<br> -This article demonstrates the development process with JUnit in the -context of multiple currency arithmetic. The corresponding source code -is in junit\samples\money. -</font> -<p><font color="#000000">You find additional samples in the -junit.samples package: -</font></p> +<h2> +<a NAME="Getting"></a>Getting Started</h2> +To get started with unit testing and JUnit read the article: +<a href="doc/cookbook/cookbook.htm">JUnit Cookbook</a>. +<br>This article describes basic test writing using JUnit 4. +<p>You find additional samples in the org.junit.samples package: <ul> - <li> - <font color="#000000">SimpleTest.java - some simple test cases</font></li> - <li> - <font color="#000000">VectorTest.java - test cases for -java.util.Vector</font></li> +<li> +SimpleTest.java - some simple test cases</li> + +<li> +VectorTest.java - test cases for java.util.Vector</li> </ul> + <h2> -<font color="#000000"><a name="Documentation"></a>Documentation</font></h2> -<blockquote><font color="#000000"><a href="doc/cookbook/cookbook.htm">JUnit -Cookbook</a> - <br> - A cookbook for implementing tests with JUnit. - <br> - <a href="doc/testinfected/testing.htm">Test Infected - Programmers +<a NAME="Documentation"></a>Documentation</h2> + +<blockquote><a href="doc/cookbook/cookbook.htm">JUnit Cookbook</a> +<br> A cookbook for implementing tests with JUnit. +<br><a href="javadoc/index.html">Javadoc</a> +<br> API documentation generated with javadoc. +<br><a href="doc/faq/faq.htm">Frequently asked questions</a> +<br> Some frequently asked questions about using JUnit. +<br><a href="cpl-v10.html">License</a> +<br> The terms of the common public license used for JUnit.<br> +</blockquote> +The following documents still describe JUnit 3.8. +<blockquote> +<br><a href="doc/testinfected/testing.htm">Test Infected - Programmers Love Writing Tests</a> - <br> - An article demonstrating the development process +<br> An article demonstrating the development process with JUnit. - <br> - <a href="doc/cookstour/cookstour.htm">JUnit - A cooks tour</a> - <br> - <a href="javadoc/index.html">Javadoc</a> - <br> - API documentation generated with javadoc. - <br> - <a href="doc/faq/faq.htm">Frequently asked questions</a> - <br> - Some frequently asked questions about using JUnit. - <br> - <a href="doc/JUnitProperties.html">TestRunner Preference settings</a> - <br> - Describes the preferences settings that can be -configured -for the JUnit TestRunners.<br> - <a href="cpl-v10.html">License</a> - <br> - The terms of the common public license used for -JUnit.</font></blockquote> -<h2> -<font color="#000000"><a name="Extending"></a>Extending JUnit</font></h2> -<font color="#000000">Examples of possible JUnit extensions can be -found in the <tt>junit.extensions</tt> -package: -</font> -<ul> - <li> - <font color="#000000"><a - href="javadoc/junit/extensions/TestDecorator.html">TestDecorator</a> -- A decorator for Test. You can use it as the base class for -implementing -decorators to extend test cases.</font></li> - <li> - <font color="#000000"><a - href="javadoc/junit/extensions/ActiveTestSuite.html">ActiveTestSuite</a> -- A TestSuite which runs each test in a separate thread and waits until -they are all terminated.</font></li> - <li> - <font color="#000000"><a - href="javadoc/junit/extensions/TestSetup.html">TestSetup</a> - A -Decorator -to set up and tear down additional fixture state. Subclass TestSetup -and -insert it into your tests when you want to set up additional state once -before the tests are run.</font></li> - <li> - <font color="#000000"><a - href="javadoc/junit/extensions/ExceptionTestCase.html">ExceptionTestCase</a> -- A TestCase that expects a particular Exception to be thrown.</font></li> -</ul> -<hr width="100%"> -<!--webbot bot="HTMLMarkup" startspan --><font color="#000000"><a - href="http://sourceforge.net"><img - src="http://sourceforge.net/sflogo.php?group_id=15278" width="88" - height="31" border="0" alt="SourceForge Logo"></a><!--webbot -bot="HTMLMarkup" endspan --></font> +<br><a href="doc/cookstour/cookstour.htm">JUnit - A cooks tour</a> +</blockquote> + +<hr WIDTH="100%"> +<!--webbot bot="HTMLMarkup" startspan --><a href="http://sourceforge.net"><IMG + src="http://sourceforge.net/sflogo.php?group_id=15278" + width="88" height="31" border="0" alt="SourceForge Logo"></a><!--webbot +bot="HTMLMarkup" endspan --> </body> </html> diff --git a/src/META-INF/MANIFEST.MF b/src/META-INF/MANIFEST.MF deleted file mode 100644 index e171a01..0000000 --- a/src/META-INF/MANIFEST.MF +++ /dev/null @@ -1,4 +0,0 @@ -Manifest-Version: 1.0
-Ant-Version: Apache Ant 1.6.5
-Created-By: 1.4.2_10-b03 (Sun Microsystems Inc.)
-
diff --git a/src/junit/awtui/AboutDialog.java b/src/junit/awtui/AboutDialog.java deleted file mode 100644 index 38f57bb..0000000 --- a/src/junit/awtui/AboutDialog.java +++ /dev/null @@ -1,77 +0,0 @@ -package junit.awtui; - -import java.awt.Button; -import java.awt.Dialog; -import java.awt.Font; -import java.awt.Frame; -import java.awt.GridBagConstraints; -import java.awt.GridBagLayout; -import java.awt.Insets; -import java.awt.Label; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.awt.event.WindowAdapter; -import java.awt.event.WindowEvent; - -import junit.runner.Version; - -class AboutDialog extends Dialog { - public AboutDialog(Frame parent) { - super(parent); - - setResizable(false); - setLayout(new GridBagLayout()); - setSize(330, 138); - setTitle("About"); - - Button button= new Button("Close"); - button.addActionListener( - new ActionListener() { - public void actionPerformed(ActionEvent e) { - dispose(); - } - } - ); - - Label label1= new Label("JUnit"); - label1.setFont(new Font("dialog", Font.PLAIN, 36)); - - Label label2= new Label("JUnit "+Version.id()+ " by Kent Beck and Erich Gamma"); - label2.setFont(new Font("dialog", Font.PLAIN, 14)); - - Logo logo= new Logo(); - - GridBagConstraints constraintsLabel1= new GridBagConstraints(); - constraintsLabel1.gridx = 3; constraintsLabel1.gridy = 0; - constraintsLabel1.gridwidth = 1; constraintsLabel1.gridheight = 1; - constraintsLabel1.anchor = GridBagConstraints.CENTER; - add(label1, constraintsLabel1); - - GridBagConstraints constraintsLabel2= new GridBagConstraints(); - constraintsLabel2.gridx = 2; constraintsLabel2.gridy = 1; - constraintsLabel2.gridwidth = 2; constraintsLabel2.gridheight = 1; - constraintsLabel2.anchor = GridBagConstraints.CENTER; - add(label2, constraintsLabel2); - - GridBagConstraints constraintsButton1= new GridBagConstraints(); - constraintsButton1.gridx = 2; constraintsButton1.gridy = 2; - constraintsButton1.gridwidth = 2; constraintsButton1.gridheight = 1; - constraintsButton1.anchor = GridBagConstraints.CENTER; - constraintsButton1.insets= new Insets(8, 0, 8, 0); - add(button, constraintsButton1); - - GridBagConstraints constraintsLogo1= new GridBagConstraints(); - constraintsLogo1.gridx = 2; constraintsLogo1.gridy = 0; - constraintsLogo1.gridwidth = 1; constraintsLogo1.gridheight = 1; - constraintsLogo1.anchor = GridBagConstraints.CENTER; - add(logo, constraintsLogo1); - - addWindowListener( - new WindowAdapter() { - public void windowClosing(WindowEvent e) { - dispose(); - } - } - ); - } -}
\ No newline at end of file diff --git a/src/junit/awtui/Logo.java b/src/junit/awtui/Logo.java deleted file mode 100644 index 4cc3b4f..0000000 --- a/src/junit/awtui/Logo.java +++ /dev/null @@ -1,58 +0,0 @@ -package junit.awtui; - -import java.awt.Canvas; -import java.awt.Graphics; -import java.awt.Image; -import java.awt.MediaTracker; -import java.awt.SystemColor; -import java.awt.Toolkit; -import java.awt.image.ImageProducer; -import java.net.URL; - -import junit.runner.BaseTestRunner; - -public class Logo extends Canvas { - private Image fImage; - private int fWidth; - private int fHeight; - - public Logo() { - fImage= loadImage("logo.gif"); - MediaTracker tracker= new MediaTracker(this); - tracker.addImage(fImage, 0); - try { - tracker.waitForAll(); - } catch (Exception e) { - } - - if (fImage != null) { - fWidth= fImage.getWidth(this); - fHeight= fImage.getHeight(this); - } else { - fWidth= 20; - fHeight= 20; - } - setSize(fWidth, fHeight); - } - - public Image loadImage(String name) { - Toolkit toolkit= Toolkit.getDefaultToolkit(); - try { - URL url= BaseTestRunner.class.getResource(name); - return toolkit.createImage((ImageProducer) url.getContent()); - } catch (Exception ex) { - } - return null; - } - - public void paint(Graphics g) { - paintBackground(g); - if (fImage != null) - g.drawImage(fImage, 0, 0, fWidth, fHeight, this); - } - - public void paintBackground( java.awt.Graphics g) { - g.setColor(SystemColor.control); - g.fillRect(0, 0, getBounds().width, getBounds().height); - } -}
\ No newline at end of file diff --git a/src/junit/awtui/ProgressBar.java b/src/junit/awtui/ProgressBar.java deleted file mode 100644 index 03e7ec2..0000000 --- a/src/junit/awtui/ProgressBar.java +++ /dev/null @@ -1,88 +0,0 @@ -package junit.awtui; - -import java.awt.Canvas; -import java.awt.Color; -import java.awt.Graphics; -import java.awt.Rectangle; -import java.awt.SystemColor; - -public class ProgressBar extends Canvas { - public boolean fError= false; - public int fTotal= 0; - public int fProgress= 0; - public int fProgressX= 0; - - public ProgressBar() { - super(); - setSize(20, 30); - } - - private Color getStatusColor() { - if (fError) - return Color.red; - return Color.green; - } - - public void paint(Graphics g) { - paintBackground(g); - paintStatus(g); - } - - public void paintBackground(Graphics g) { - g.setColor(SystemColor.control); - Rectangle r= getBounds(); - g.fillRect(0, 0, r.width, r.height); - g.setColor(Color.darkGray); - g.drawLine(0, 0, r.width-1, 0); - g.drawLine(0, 0, 0, r.height-1); - g.setColor(Color.white); - g.drawLine(r.width-1, 0, r.width-1, r.height-1); - g.drawLine(0, r.height-1, r.width-1, r.height-1); - } - - public void paintStatus(Graphics g) { - g.setColor(getStatusColor()); - Rectangle r= new Rectangle(0, 0, fProgressX, getBounds().height); - g.fillRect(1, 1, r.width-1, r.height-2); - } - - private void paintStep(int startX, int endX) { - repaint(startX, 1, endX-startX, getBounds().height-2); - } - - public void reset() { - fProgressX= 1; - fProgress= 0; - fError= false; - paint(getGraphics()); - } - - public int scale(int value) { - if (fTotal > 0) - return Math.max(1, value*(getBounds().width-1)/fTotal); - return value; - } - - public void setBounds(int x, int y, int w, int h) { - super.setBounds(x, y, w, h); - fProgressX= scale(fProgress); - } - - public void start(int total) { - fTotal= total; - reset(); - } - - public void step(boolean successful) { - fProgress++; - int x= fProgressX; - - fProgressX= scale(fProgress); - - if (!fError && !successful) { - fError= true; - x= 1; - } - paintStep(x, fProgressX); - } -}
\ No newline at end of file diff --git a/src/junit/awtui/TestRunner.java b/src/junit/awtui/TestRunner.java deleted file mode 100644 index cb735d5..0000000 --- a/src/junit/awtui/TestRunner.java +++ /dev/null @@ -1,571 +0,0 @@ -package junit.awtui; - -import java.awt.BorderLayout; -import java.awt.Button; -import java.awt.Checkbox; -import java.awt.Color; -import java.awt.Component; -import java.awt.Font; -import java.awt.Frame; -import java.awt.GridBagConstraints; -import java.awt.GridBagLayout; -import java.awt.GridLayout; -import java.awt.Image; -import java.awt.Insets; -import java.awt.Label; -import java.awt.List; -import java.awt.Menu; -import java.awt.MenuBar; -import java.awt.MenuItem; -import java.awt.Panel; -import java.awt.SystemColor; -import java.awt.TextArea; -import java.awt.TextField; -import java.awt.Toolkit; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.awt.event.ItemEvent; -import java.awt.event.ItemListener; -import java.awt.event.TextEvent; -import java.awt.event.TextListener; -import java.awt.event.WindowAdapter; -import java.awt.event.WindowEvent; -import java.awt.image.ImageProducer; -import java.util.Vector; - -import junit.framework.Test; -import junit.framework.TestCase; -import junit.framework.TestResult; -import junit.framework.TestSuite; -import junit.runner.BaseTestRunner; -import junit.runner.TestRunListener; - -/** - * An AWT based user interface to run tests. - * Enter the name of a class which either provides a static - * suite method or is a subclass of TestCase. - * <pre> - * Synopsis: java junit.awtui.TestRunner [-noloading] [TestCase] - * </pre> - * TestRunner takes as an optional argument the name of the testcase class to be run. - */ - public class TestRunner extends BaseTestRunner { - protected Frame fFrame; - protected Vector fExceptions; - protected Vector fFailedTests; - protected Thread fRunner; - protected TestResult fTestResult; - - protected TextArea fTraceArea; - protected TextField fSuiteField; - protected Button fRun; - protected ProgressBar fProgressIndicator; - protected List fFailureList; - protected Logo fLogo; - protected Label fNumberOfErrors; - protected Label fNumberOfFailures; - protected Label fNumberOfRuns; - protected Button fQuitButton; - protected Button fRerunButton; - protected TextField fStatusLine; - protected Checkbox fUseLoadingRunner; - - protected static final Font PLAIN_FONT= new Font("dialog", Font.PLAIN, 12); - private static final int GAP= 4; - - public TestRunner() { - } - - private void about() { - AboutDialog about= new AboutDialog(fFrame); - about.setModal(true); - about.setLocation(300, 300); - about.setVisible(true); - } - - public void testStarted(String testName) { - showInfo("Running: "+testName); - } - - public void testEnded(String testName) { - setLabelValue(fNumberOfRuns, fTestResult.runCount()); - synchronized(this) { - fProgressIndicator.step(fTestResult.wasSuccessful()); - } - } - - public void testFailed(int status, Test test, Throwable t) { - switch (status) { - case TestRunListener.STATUS_ERROR: - fNumberOfErrors.setText(Integer.toString(fTestResult.errorCount())); - appendFailure("Error", test, t); - break; - case TestRunListener.STATUS_FAILURE: - fNumberOfFailures.setText(Integer.toString(fTestResult.failureCount())); - appendFailure("Failure", test, t); - break; - } - } - - protected void addGrid(Panel p, Component co, int x, int y, int w, int fill, double wx, int anchor) { - GridBagConstraints c= new GridBagConstraints(); - c.gridx= x; c.gridy= y; - c.gridwidth= w; - c.anchor= anchor; - c.weightx= wx; - c.fill= fill; - if (fill == GridBagConstraints.BOTH || fill == GridBagConstraints.VERTICAL) - c.weighty= 1.0; - c.insets= new Insets(y == 0 ? GAP : 0, x == 0 ? GAP : 0, GAP, GAP); - p.add(co, c); - } - - private void appendFailure(String kind, Test test, Throwable t) { - kind+= ": " + test; - String msg= t.getMessage(); - if (msg != null) { - kind+= ":" + truncate(msg); - } - fFailureList.add(kind); - fExceptions.addElement(t); - fFailedTests.addElement(test); - if (fFailureList.getItemCount() == 1) { - fFailureList.select(0); - failureSelected(); - } - } - /** - * Creates the JUnit menu. Clients override this - * method to add additional menu items. - */ - protected Menu createJUnitMenu() { - Menu menu= new Menu("JUnit"); - MenuItem mi= new MenuItem("About..."); - mi.addActionListener( - new ActionListener() { - public void actionPerformed(ActionEvent event) { - about(); - } - } - ); - menu.add(mi); - - menu.addSeparator(); - mi= new MenuItem("Exit"); - mi.addActionListener( - new ActionListener() { - public void actionPerformed(ActionEvent event) { - System.exit(0); - } - } - ); - menu.add(mi); - return menu; - } - - protected void createMenus(MenuBar mb) { - mb.add(createJUnitMenu()); - } - protected TestResult createTestResult() { - return new TestResult(); - } - - protected Frame createUI(String suiteName) { - Frame frame= new Frame("JUnit"); - Image icon= loadFrameIcon(); - if (icon != null) - frame.setIconImage(icon); - - frame.setLayout(new BorderLayout(0, 0)); - frame.setBackground(SystemColor.control); - final Frame finalFrame= frame; - - frame.addWindowListener( - new WindowAdapter() { - public void windowClosing(WindowEvent e) { - finalFrame.dispose(); - System.exit(0); - } - } - ); - - MenuBar mb = new MenuBar(); - createMenus(mb); - frame.setMenuBar(mb); - - //---- first section - Label suiteLabel= new Label("Test class name:"); - - fSuiteField= new TextField(suiteName != null ? suiteName : ""); - fSuiteField.selectAll(); - fSuiteField.requestFocus(); - fSuiteField.setFont(PLAIN_FONT); - fSuiteField.setColumns(40); - fSuiteField.addActionListener( - new ActionListener() { - public void actionPerformed(ActionEvent e) { - runSuite(); - } - } - ); - fSuiteField.addTextListener( - new TextListener() { - public void textValueChanged(TextEvent e) { - fRun.setEnabled(fSuiteField.getText().length() > 0); - fStatusLine.setText(""); - } - } - ); - fRun= new Button("Run"); - fRun.setEnabled(false); - fRun.addActionListener( - new ActionListener() { - public void actionPerformed(ActionEvent e) { - runSuite(); - } - } - ); - boolean useLoader= useReloadingTestSuiteLoader(); - fUseLoadingRunner= new Checkbox("Reload classes every run", useLoader); - if (inVAJava()) - fUseLoadingRunner.setVisible(false); - - //---- second section - fProgressIndicator= new ProgressBar(); - - //---- third section - fNumberOfErrors= new Label("0000", Label.RIGHT); - fNumberOfErrors.setText("0"); - fNumberOfErrors.setFont(PLAIN_FONT); - - fNumberOfFailures= new Label("0000", Label.RIGHT); - fNumberOfFailures.setText("0"); - fNumberOfFailures.setFont(PLAIN_FONT); - - fNumberOfRuns= new Label("0000", Label.RIGHT); - fNumberOfRuns.setText("0"); - fNumberOfRuns.setFont(PLAIN_FONT); - - Panel numbersPanel= createCounterPanel(); - - //---- fourth section - Label failureLabel= new Label("Errors and Failures:"); - - fFailureList= new List(5); - fFailureList.addItemListener( - new ItemListener() { - public void itemStateChanged(ItemEvent e) { - failureSelected(); - } - } - ); - fRerunButton= new Button("Run"); - fRerunButton.setEnabled(false); - fRerunButton.addActionListener( - new ActionListener() { - public void actionPerformed(ActionEvent e) { - rerun(); - } - } - ); - - Panel failedPanel= new Panel(new GridLayout(0, 1, 0, 2)); - failedPanel.add(fRerunButton); - - fTraceArea= new TextArea(); - fTraceArea.setRows(5); - fTraceArea.setColumns(60); - - //---- fifth section - fStatusLine= new TextField(); - fStatusLine.setFont(PLAIN_FONT); - fStatusLine.setEditable(false); - fStatusLine.setForeground(Color.red); - - fQuitButton= new Button("Exit"); - fQuitButton.addActionListener( - new ActionListener() { - public void actionPerformed(ActionEvent e) { - System.exit(0); - } - } - ); - - // --------- - fLogo= new Logo(); - - //---- overall layout - Panel panel= new Panel(new GridBagLayout()); - - addGrid(panel, suiteLabel, 0, 0, 2, GridBagConstraints.HORIZONTAL, 1.0, GridBagConstraints.WEST); - - addGrid(panel, fSuiteField, 0, 1, 2, GridBagConstraints.HORIZONTAL, 1.0, GridBagConstraints.WEST); - addGrid(panel, fRun, 2, 1, 1, GridBagConstraints.HORIZONTAL, 0.0, GridBagConstraints.CENTER); - addGrid(panel, fUseLoadingRunner, 0, 2, 2, GridBagConstraints.NONE, 1.0, GridBagConstraints.WEST); - addGrid(panel, fProgressIndicator, 0, 3, 2, GridBagConstraints.HORIZONTAL, 1.0, GridBagConstraints.WEST); - addGrid(panel, fLogo, 2, 3, 1, GridBagConstraints.NONE, 0.0, GridBagConstraints.NORTH); - - addGrid(panel, numbersPanel, 0, 4, 2, GridBagConstraints.NONE, 0.0, GridBagConstraints.WEST); - - addGrid(panel, failureLabel, 0, 5, 2, GridBagConstraints.HORIZONTAL, 1.0, GridBagConstraints.WEST); - addGrid(panel, fFailureList, 0, 6, 2, GridBagConstraints.BOTH, 1.0, GridBagConstraints.WEST); - addGrid(panel, failedPanel, 2, 6, 1, GridBagConstraints.HORIZONTAL, 0.0, GridBagConstraints.CENTER); - addGrid(panel, fTraceArea, 0, 7, 2, GridBagConstraints.BOTH, 1.0, GridBagConstraints.WEST); - - addGrid(panel, fStatusLine, 0, 8, 2, GridBagConstraints.HORIZONTAL, 1.0, GridBagConstraints.CENTER); - addGrid(panel, fQuitButton, 2, 8, 1, GridBagConstraints.HORIZONTAL, 0.0, GridBagConstraints.CENTER); - - frame.add(panel, BorderLayout.CENTER); - frame.pack(); - return frame; - } - - protected Panel createCounterPanel() { - Panel numbersPanel= new Panel(new GridBagLayout()); - addToCounterPanel( - numbersPanel, - new Label("Runs:"), - 0, 0, 1, 1, 0.0, 0.0, - GridBagConstraints.CENTER, GridBagConstraints.NONE, - new Insets(0, 0, 0, 0) - ); - addToCounterPanel( - numbersPanel, - fNumberOfRuns, - 1, 0, 1, 1, 0.33, 0.0, - GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, - new Insets(0, 8, 0, 40) - ); - addToCounterPanel( - numbersPanel, - new Label("Errors:"), - 2, 0, 1, 1, 0.0, 0.0, - GridBagConstraints.CENTER, GridBagConstraints.NONE, - new Insets(0, 8, 0, 0) - ); - addToCounterPanel( - numbersPanel, - fNumberOfErrors, - 3, 0, 1, 1, 0.33, 0.0, - GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, - new Insets(0, 8, 0, 40) - ); - addToCounterPanel( - numbersPanel, - new Label("Failures:"), - 4, 0, 1, 1, 0.0, 0.0, - GridBagConstraints.CENTER, GridBagConstraints.NONE, - new Insets(0, 8, 0, 0) - ); - addToCounterPanel( - numbersPanel, - fNumberOfFailures, - 5, 0, 1, 1, 0.33, 0.0, - GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, - new Insets(0, 8, 0, 0) - ); - return numbersPanel; - } - - private void addToCounterPanel(Panel counter, Component comp, - int gridx, int gridy, int gridwidth, int gridheight, - double weightx, double weighty, - int anchor, int fill, - Insets insets) { - - GridBagConstraints constraints= new GridBagConstraints(); - constraints.gridx= gridx; - constraints.gridy= gridy; - constraints.gridwidth= gridwidth; - constraints.gridheight= gridheight; - constraints.weightx= weightx; - constraints.weighty= weighty; - constraints.anchor= anchor; - constraints.fill= fill; - constraints.insets= insets; - counter.add(comp, constraints); - } - - - public void failureSelected() { - fRerunButton.setEnabled(isErrorSelected()); - showErrorTrace(); - } - - private boolean isErrorSelected() { - return fFailureList.getSelectedIndex() != -1; - } - - private Image loadFrameIcon() { - Toolkit toolkit= Toolkit.getDefaultToolkit(); - try { - java.net.URL url= BaseTestRunner.class.getResource("smalllogo.gif"); - return toolkit.createImage((ImageProducer) url.getContent()); - } catch (Exception ex) { - } - return null; - } - - public Thread getRunner() { - return fRunner; - } - - public static void main(String[] args) { - new TestRunner().start(args); - } - - public static void run(Class test) { - String args[]= { test.getName() }; - main(args); - } - - public void rerun() { - int index= fFailureList.getSelectedIndex(); - if (index == -1) - return; - - Test test= (Test)fFailedTests.elementAt(index); - rerunTest(test); - } - - private void rerunTest(Test test) { - if (!(test instanceof TestCase)) { - showInfo("Could not reload "+ test.toString()); - return; - } - Test reloadedTest= null; - TestCase rerunTest= (TestCase)test; - try { - Class reloadedTestClass= getLoader().reload(test.getClass()); - reloadedTest= TestSuite.createTest(reloadedTestClass, rerunTest.getName()); - } catch(Exception e) { - showInfo("Could not reload "+ test.toString()); - return; - } - TestResult result= new TestResult(); - reloadedTest.run(result); - - String message= reloadedTest.toString(); - if(result.wasSuccessful()) - showInfo(message+" was successful"); - else if (result.errorCount() == 1) - showStatus(message+" had an error"); - else - showStatus(message+" had a failure"); - } - - protected void reset() { - setLabelValue(fNumberOfErrors, 0); - setLabelValue(fNumberOfFailures, 0); - setLabelValue(fNumberOfRuns, 0); - fProgressIndicator.reset(); - fRerunButton.setEnabled(false); - fFailureList.removeAll(); - fExceptions= new Vector(10); - fFailedTests= new Vector(10); - fTraceArea.setText(""); - - } - - protected void runFailed(String message) { - showStatus(message); - fRun.setLabel("Run"); - fRunner= null; - } - - synchronized public void runSuite() { - if (fRunner != null && fTestResult != null) { - fTestResult.stop(); - } else { - setLoading(shouldReload()); - fRun.setLabel("Stop"); - showInfo("Initializing..."); - reset(); - - showInfo("Load Test Case..."); - - final Test testSuite= getTest(fSuiteField.getText()); - if (testSuite != null) { - fRunner= new Thread() { - public void run() { - fTestResult= createTestResult(); - fTestResult.addListener(TestRunner.this); - fProgressIndicator.start(testSuite.countTestCases()); - showInfo("Running..."); - - long startTime= System.currentTimeMillis(); - testSuite.run(fTestResult); - - if (fTestResult.shouldStop()) { - showStatus("Stopped"); - } else { - long endTime= System.currentTimeMillis(); - long runTime= endTime-startTime; - showInfo("Finished: " + elapsedTimeAsString(runTime) + " seconds"); - } - fTestResult= null; - fRun.setLabel("Run"); - fRunner= null; - System.gc(); - } - }; - fRunner.start(); - } - } - } - - private boolean shouldReload() { - return !inVAJava() && fUseLoadingRunner.getState(); - } - - private void setLabelValue(Label label, int value) { - label.setText(Integer.toString(value)); - label.invalidate(); - label.getParent().validate(); - - } - - public void setSuiteName(String suite) { - fSuiteField.setText(suite); - } - - private void showErrorTrace() { - int index= fFailureList.getSelectedIndex(); - if (index == -1) - return; - - Throwable t= (Throwable) fExceptions.elementAt(index); - fTraceArea.setText(getFilteredTrace(t)); - } - - - private void showInfo(String message) { - fStatusLine.setFont(PLAIN_FONT); - fStatusLine.setForeground(Color.black); - fStatusLine.setText(message); - } - - protected void clearStatus() { - showStatus(""); - } - - private void showStatus(String status) { - fStatusLine.setFont(PLAIN_FONT); - fStatusLine.setForeground(Color.red); - fStatusLine.setText(status); - } - /** - * Starts the TestRunner - */ - public void start(String[] args) { - String suiteName= processArguments(args); - fFrame= createUI(suiteName); - fFrame.setLocation(200, 200); - fFrame.setVisible(true); - - if (suiteName != null) { - setSuiteName(suiteName); - runSuite(); - } - } -}
\ No newline at end of file diff --git a/src/junit/extensions/ActiveTestSuite.java b/src/junit/extensions/ActiveTestSuite.java index 9fc4edd..0623565 100644 --- a/src/junit/extensions/ActiveTestSuite.java +++ b/src/junit/extensions/ActiveTestSuite.java @@ -1,6 +1,7 @@ package junit.extensions; import junit.framework.Test; +import junit.framework.TestCase; import junit.framework.TestResult; import junit.framework.TestSuite; @@ -16,7 +17,7 @@ public class ActiveTestSuite extends TestSuite { public ActiveTestSuite() { } - public ActiveTestSuite(Class theClass) { + public ActiveTestSuite(Class<? extends TestCase> theClass) { super(theClass); } @@ -24,18 +25,21 @@ public class ActiveTestSuite extends TestSuite { super (name); } - public ActiveTestSuite(Class theClass, String name) { + public ActiveTestSuite(Class<? extends TestCase> theClass, String name) { super(theClass, name); } + @Override public void run(TestResult result) { fActiveTestDeathCount= 0; super.run(result); waitUntilFinished(); } + @Override public void runTest(final Test test, final TestResult result) { Thread t= new Thread() { + @Override public void run() { try { // inlined due to limitation in VA/Java diff --git a/src/junit/extensions/ExceptionTestCase.java b/src/junit/extensions/ExceptionTestCase.java deleted file mode 100644 index 7004085..0000000 --- a/src/junit/extensions/ExceptionTestCase.java +++ /dev/null @@ -1,46 +0,0 @@ -package junit.extensions; - -import junit.framework.TestCase; - -/** - * A TestCase that expects an Exception of class fExpected to be thrown. - * The other way to check that an expected exception is thrown is: - * <pre> - * try { - * shouldThrow(); - * } - * catch (SpecialException e) { - * return; - * } - * fail("Expected SpecialException"); - * </pre> - * - * To use ExceptionTestCase, create a TestCase like: - * <pre> - * new ExceptionTestCase("testShouldThrow", SpecialException.class); - * </pre> - */ -public class ExceptionTestCase extends TestCase { - Class fExpected; - - public ExceptionTestCase(String name, Class exception) { - super(name); - fExpected= exception; - } - /** - * Execute the test method expecting that an Exception of - * class fExpected or one of its subclasses will be thrown - */ - protected void runTest() throws Throwable { - try { - super.runTest(); - } - catch (Exception e) { - if (fExpected.isAssignableFrom(e.getClass())) - return; - else - throw e; - } - fail("Expected exception " + fExpected); - } -}
\ No newline at end of file diff --git a/src/junit/extensions/RepeatedTest.java b/src/junit/extensions/RepeatedTest.java index be5c439..3b687a5 100644 --- a/src/junit/extensions/RepeatedTest.java +++ b/src/junit/extensions/RepeatedTest.java @@ -5,20 +5,24 @@ import junit.framework.TestResult; /** * A Decorator that runs a test repeatedly. - * + * */ -public class RepeatedTest extends TestDecorator { +public class RepeatedTest extends TestDecorator { private int fTimesRepeat; public RepeatedTest(Test test, int repeat) { super(test); if (repeat < 0) - throw new IllegalArgumentException("Repetition count must be > 0"); + throw new IllegalArgumentException("Repetition count must be >= 0"); fTimesRepeat= repeat; } + + @Override public int countTestCases() { - return super.countTestCases()*fTimesRepeat; + return super.countTestCases() * fTimesRepeat; } + + @Override public void run(TestResult result) { for (int i= 0; i < fTimesRepeat; i++) { if (result.shouldStop()) @@ -26,7 +30,9 @@ public class RepeatedTest extends TestDecorator { super.run(result); } } + + @Override public String toString() { - return super.toString()+"(repeated)"; + return super.toString() + "(repeated)"; } }
\ No newline at end of file diff --git a/src/junit/extensions/TestDecorator.java b/src/junit/extensions/TestDecorator.java index 2111e8f..d9ae474 100644 --- a/src/junit/extensions/TestDecorator.java +++ b/src/junit/extensions/TestDecorator.java @@ -5,11 +5,10 @@ import junit.framework.Test; import junit.framework.TestResult; /** - * A Decorator for Tests. Use TestDecorator as the base class - * for defining new test decorators. Test decorator subclasses - * can be introduced to add behaviour before or after a test - * is run. - * + * A Decorator for Tests. Use TestDecorator as the base class for defining new + * test decorators. Test decorator subclasses can be introduced to add behaviour + * before or after a test is run. + * */ public class TestDecorator extends Assert implements Test { protected Test fTest; @@ -17,19 +16,23 @@ public class TestDecorator extends Assert implements Test { public TestDecorator(Test test) { fTest= test; } + /** * The basic run behaviour. */ public void basicRun(TestResult result) { fTest.run(result); } + public int countTestCases() { return fTest.countTestCases(); } + public void run(TestResult result) { basicRun(result); } + @Override public String toString() { return fTest.toString(); } diff --git a/src/junit/extensions/TestSetup.java b/src/junit/extensions/TestSetup.java index 9ee8b05..00dcd21 100644 --- a/src/junit/extensions/TestSetup.java +++ b/src/junit/extensions/TestSetup.java @@ -5,15 +5,17 @@ import junit.framework.Test; import junit.framework.TestResult; /** - * A Decorator to set up and tear down additional fixture state. - * Subclass TestSetup and insert it into your tests when you want - * to set up additional state once before the tests are run. + * A Decorator to set up and tear down additional fixture state. Subclass + * TestSetup and insert it into your tests when you want to set up additional + * state once before the tests are run. */ public class TestSetup extends TestDecorator { public TestSetup(Test test) { super(test); } + + @Override public void run(final TestResult result) { Protectable p= new Protectable() { public void protect() throws Exception { @@ -24,15 +26,16 @@ public class TestSetup extends TestDecorator { }; result.runProtected(this, p); } + /** - * Sets up the fixture. Override to set up additional fixture - * state. + * Sets up the fixture. Override to set up additional fixture state. */ protected void setUp() throws Exception { } + /** - * Tears down the fixture. Override to tear down the additional - * fixture state. + * Tears down the fixture. Override to tear down the additional fixture + * state. */ protected void tearDown() throws Exception { } diff --git a/src/junit/extensions/package-info.java b/src/junit/extensions/package-info.java new file mode 100644 index 0000000..a1c5bb4 --- /dev/null +++ b/src/junit/extensions/package-info.java @@ -0,0 +1,4 @@ +/** + * Provides extended functionality for JUnit v3.x. + */ +package junit.extensions;
\ No newline at end of file diff --git a/src/junit/framework/Assert.java b/src/junit/framework/Assert.java index ec6e838..3dcc23d 100644 --- a/src/junit/framework/Assert.java +++ b/src/junit/framework/Assert.java @@ -44,6 +44,9 @@ public class Assert { * Fails a test with the given message. */ static public void fail(String message) { + if (message == null) { + throw new AssertionFailedError(); + } throw new AssertionFailedError(message); } /** @@ -78,7 +81,8 @@ public class Assert { return; if (expected != null && expected.equals(actual)) return; - throw new ComparisonFailure(message, expected, actual); + String cleanMessage= message == null ? "" : message; + throw new ComparisonFailure(cleanMessage, expected, actual); } /** * Asserts that two Strings are equal. @@ -105,18 +109,15 @@ public class Assert { assertEquals(null, expected, actual, delta); } /** - * Asserts that two floats are equal concerning a delta. If they are not - * an AssertionFailedError is thrown with the given message. If the expected - * value is infinity then the delta value is ignored. + * Asserts that two floats are equal concerning a positive delta. If they + * are not an AssertionFailedError is thrown with the given message. If the + * expected value is infinity then the delta value is ignored. */ static public void assertEquals(String message, float expected, float actual, float delta) { - // handle infinity specially since subtracting to infinite values gives NaN and the - // the following test fails - if (Float.isInfinite(expected)) { - if (!(expected == actual)) + if (Float.compare(expected, actual) == 0) + return; + if (!(Math.abs(expected - actual) <= delta)) failNotEquals(message, new Float(expected), new Float(actual)); - } else if (!(Math.abs(expected-actual) <= delta)) - failNotEquals(message, new Float(expected), new Float(actual)); } /** * Asserts that two floats are equal concerning a delta. If the expected @@ -217,10 +218,16 @@ public class Assert { assertTrue(message, object != null); } /** - * Asserts that an object is null. + * Asserts that an object is null. If it isn't an {@link AssertionError} is + * thrown. + * Message contains: Expected: <null> but was: object + * + * @param object + * Object to check or <code>null</code> */ static public void assertNull(Object object) { - assertNull(null, object); + String message = "Expected: <null> but was: " + String.valueOf(object); + assertNull(message, object); } /** * Asserts that an object is null. If it is not @@ -280,9 +287,9 @@ public class Assert { fail(format(message, expected, actual)); } - static String format(String message, Object expected, Object actual) { + public static String format(String message, Object expected, Object actual) { String formatted= ""; - if (message != null) + if (message != null && message.length() > 0) formatted= message+" "; return formatted+"expected:<"+expected+"> but was:<"+actual+">"; } diff --git a/src/junit/framework/AssertionFailedError.java b/src/junit/framework/AssertionFailedError.java index b041f06..0d7802c 100644 --- a/src/junit/framework/AssertionFailedError.java +++ b/src/junit/framework/AssertionFailedError.java @@ -3,13 +3,18 @@ package junit.framework; /** * Thrown when an assertion failed. */ -public class AssertionFailedError extends Error { +public class AssertionFailedError extends AssertionError { private static final long serialVersionUID= 1L; - - public AssertionFailedError () { + + public AssertionFailedError() { + } + + public AssertionFailedError(String message) { + super(defaultString(message)); } - public AssertionFailedError (String message) { - super (message); + + private static String defaultString(String message) { + return message == null ? "" : message; } }
\ No newline at end of file diff --git a/src/junit/framework/ComparisonFailure.java b/src/junit/framework/ComparisonFailure.java index c31068f..5077993 100644 --- a/src/junit/framework/ComparisonFailure.java +++ b/src/junit/framework/ComparisonFailure.java @@ -28,8 +28,9 @@ public class ComparisonFailure extends AssertionFailedError { * Returns "..." in place of common prefix and "..." in * place of common suffix between expected and actual. * - * @see java.lang.Throwable#getMessage() + * @see Throwable#getMessage() */ + @Override public String getMessage() { return new ComparisonCompactor(MAX_CONTEXT_LENGTH, fExpected, fActual).compact(super.getMessage()); } diff --git a/src/junit/framework/JUnit4TestAdapter.java b/src/junit/framework/JUnit4TestAdapter.java new file mode 100644 index 0000000..a05a313 --- /dev/null +++ b/src/junit/framework/JUnit4TestAdapter.java @@ -0,0 +1,85 @@ +package junit.framework; + +import java.util.List; + +import org.junit.Ignore; +import org.junit.runner.Describable; +import org.junit.runner.Description; +import org.junit.runner.Request; +import org.junit.runner.Runner; +import org.junit.runner.manipulation.Filter; +import org.junit.runner.manipulation.Filterable; +import org.junit.runner.manipulation.NoTestsRemainException; +import org.junit.runner.manipulation.Sortable; +import org.junit.runner.manipulation.Sorter; + +public class JUnit4TestAdapter implements Test, Filterable, Sortable, Describable { + private final Class<?> fNewTestClass; + + private final Runner fRunner; + + private final JUnit4TestAdapterCache fCache; + + public JUnit4TestAdapter(Class<?> newTestClass) { + this(newTestClass, JUnit4TestAdapterCache.getDefault()); + } + + public JUnit4TestAdapter(final Class<?> newTestClass, + JUnit4TestAdapterCache cache) { + fCache = cache; + fNewTestClass = newTestClass; + fRunner = Request.classWithoutSuiteMethod(newTestClass).getRunner(); + } + + public int countTestCases() { + return fRunner.testCount(); + } + + public void run(TestResult result) { + fRunner.run(fCache.getNotifier(result, this)); + } + + // reflective interface for Eclipse + public List<Test> getTests() { + return fCache.asTestList(getDescription()); + } + + // reflective interface for Eclipse + public Class<?> getTestClass() { + return fNewTestClass; + } + + public Description getDescription() { + Description description= fRunner.getDescription(); + return removeIgnored(description); + } + + private Description removeIgnored(Description description) { + if (isIgnored(description)) + return Description.EMPTY; + Description result = description.childlessCopy(); + for (Description each : description.getChildren()) { + Description child= removeIgnored(each); + if (! child.isEmpty()) + result.addChild(child); + } + return result; + } + + private boolean isIgnored(Description description) { + return description.getAnnotation(Ignore.class) != null; + } + + @Override + public String toString() { + return fNewTestClass.getName(); + } + + public void filter(Filter filter) throws NoTestsRemainException { + filter.apply(fRunner); + } + + public void sort(Sorter sorter) { + sorter.apply(fRunner); + } +}
\ No newline at end of file diff --git a/src/junit/framework/JUnit4TestAdapterCache.java b/src/junit/framework/JUnit4TestAdapterCache.java new file mode 100644 index 0000000..26175c5 --- /dev/null +++ b/src/junit/framework/JUnit4TestAdapterCache.java @@ -0,0 +1,81 @@ +/** + * + */ +package junit.framework; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; + +import org.junit.runner.Description; +import org.junit.runner.notification.Failure; +import org.junit.runner.notification.RunListener; +import org.junit.runner.notification.RunNotifier; + +public class JUnit4TestAdapterCache extends HashMap<Description, Test> { + private static final long serialVersionUID = 1L; + private static final JUnit4TestAdapterCache fInstance = new JUnit4TestAdapterCache(); + + public static JUnit4TestAdapterCache getDefault() { + return fInstance; + } + + public Test asTest(Description description) { + if (description.isSuite()) + return createTest(description); + else { + if (!containsKey(description)) + put(description, createTest(description)); + return get(description); + } + } + + Test createTest(Description description) { + if (description.isTest()) + return new JUnit4TestCaseFacade(description); + else { + TestSuite suite = new TestSuite(description.getDisplayName()); + for (Description child : description.getChildren()) + suite.addTest(asTest(child)); + return suite; + } + } + + public RunNotifier getNotifier(final TestResult result, + final JUnit4TestAdapter adapter) { + RunNotifier notifier = new RunNotifier(); + notifier.addListener(new RunListener() { + @Override + public void testFailure(Failure failure) throws Exception { + result.addError(asTest(failure.getDescription()), failure.getException()); + } + + @Override + public void testFinished(Description description) + throws Exception { + result.endTest(asTest(description)); + } + + @Override + public void testStarted(Description description) + throws Exception { + result.startTest(asTest(description)); + } + }); + return notifier; + } + + public List<Test> asTestList(Description description) { + if (description.isTest()) + return Arrays.asList(asTest(description)); + else { + List<Test> returnThis = new ArrayList<Test>(); + for (Description child : description.getChildren()) { + returnThis.add(asTest(child)); + } + return returnThis; + } + } + +}
\ No newline at end of file diff --git a/src/junit/framework/JUnit4TestCaseFacade.java b/src/junit/framework/JUnit4TestCaseFacade.java new file mode 100644 index 0000000..fd43822 --- /dev/null +++ b/src/junit/framework/JUnit4TestCaseFacade.java @@ -0,0 +1,33 @@ +/** + * + */ +package junit.framework; + +import org.junit.runner.Describable; +import org.junit.runner.Description; + +public class JUnit4TestCaseFacade implements Test, Describable { + private final Description fDescription; + + JUnit4TestCaseFacade(Description description) { + fDescription = description; + } + + @Override + public String toString() { + return getDescription().toString(); + } + + public int countTestCases() { + return 1; + } + + public void run(TestResult result) { + throw new RuntimeException( + "This test stub created only for informational purposes."); + } + + public Description getDescription() { + return fDescription; + } +}
\ No newline at end of file diff --git a/src/junit/framework/TestCase.java b/src/junit/framework/TestCase.java index e54b16b..b047ec9 100644 --- a/src/junit/framework/TestCase.java +++ b/src/junit/framework/TestCase.java @@ -5,48 +5,52 @@ import java.lang.reflect.Method; import java.lang.reflect.Modifier; /** - * A test case defines the fixture to run multiple tests. To define a test case<br> - * 1) implement a subclass of TestCase<br> - * 2) define instance variables that store the state of the fixture<br> - * 3) initialize the fixture state by overriding <code>setUp</code><br> - * 4) clean-up after a test by overriding <code>tearDown</code>.<br> + * A test case defines the fixture to run multiple tests. To define a test case<br/> + * <ol> + * <li>implement a subclass of <code>TestCase</code></li> + * <li>define instance variables that store the state of the fixture</li> + * <li>initialize the fixture state by overriding {@link #setUp()}</li> + * <li>clean-up after a test by overriding {@link #tearDown()}.</li> + * </ol> * Each test runs in its own fixture so there * can be no side effects among test runs. * Here is an example: * <pre> * public class MathTest extends TestCase { - * protected double fValue1; - * protected double fValue2; + * protected double fValue1; + * protected double fValue2; * * protected void setUp() { - * fValue1= 2.0; - * fValue2= 3.0; - * } + * fValue1= 2.0; + * fValue2= 3.0; + * } * } * </pre> * * For each test implement a method which interacts * with the fixture. Verify the expected results with assertions specified - * by calling <code>assertTrue</code> with a boolean. + * by calling {@link junit.framework.Assert#assertTrue(String, boolean)} with a boolean. * <pre> * public void testAdd() { - * double result= fValue1 + fValue2; - * assertTrue(result == 5.0); + * double result= fValue1 + fValue2; + * assertTrue(result == 5.0); * } * </pre> + * * Once the methods are defined you can run them. The framework supports * both a static type safe and more dynamic way to run a test. * In the static way you override the runTest method and define the method to * be invoked. A convenient way to do so is with an anonymous inner class. * <pre> * TestCase test= new MathTest("add") { - * public void runTest() { - * testAdd(); - * } + * public void runTest() { + * testAdd(); + * } * }; * test.run(); * </pre> - * The dynamic way uses reflection to implement <code>runTest</code>. It dynamically finds + * + * The dynamic way uses reflection to implement {@link #runTest()}. It dynamically finds * and invokes a method. * In this case the name of the test case has to correspond to the test method * to be run. @@ -54,21 +58,21 @@ import java.lang.reflect.Modifier; * TestCase test= new MathTest("testAdd"); * test.run(); * </pre> + * * The tests to be run can be collected into a TestSuite. JUnit provides * different <i>test runners</i> which can run a test suite and collect the results. * A test runner either expects a static method <code>suite</code> as the entry * point to get a test to run or it will extract the suite automatically. * <pre> * public static Test suite() { - * suite.addTest(new MathTest("testAdd")); - * suite.addTest(new MathTest("testDivideByZero")); - * return suite; - * } + * suite.addTest(new MathTest("testAdd")); + * suite.addTest(new MathTest("testDivideByZero")); + * return suite; + * } * </pre> * @see TestResult * @see TestSuite */ - public abstract class TestCase extends Assert implements Test { /** * the name of the test case @@ -121,7 +125,7 @@ public abstract class TestCase extends Assert implements Test { } /** * Runs the bare test sequence. - * @exception Throwable if any exception is thrown + * @throws Throwable if any exception is thrown */ public void runBare() throws Throwable { Throwable exception= null; @@ -142,10 +146,10 @@ public abstract class TestCase extends Assert implements Test { } /** * Override to run the test and assert its state. - * @exception Throwable if any exception is thrown + * @throws Throwable if any exception is thrown */ protected void runTest() throws Throwable { - assertNotNull(fName); // Some VMs crash when calling getMethod(null,null); + assertNotNull("TestCase.fName cannot be null", fName); // Some VMs crash when calling getMethod(null,null); Method runMethod= null; try { // use getMethod to get all public inherited @@ -161,7 +165,7 @@ public abstract class TestCase extends Assert implements Test { } try { - runMethod.invoke(this, (Object[])new Class[0]); + runMethod.invoke(this); } catch (InvocationTargetException e) { e.fillInStackTrace(); @@ -187,19 +191,20 @@ public abstract class TestCase extends Assert implements Test { /** * Returns a string representation of the test case */ + @Override public String toString() { return getName() + "(" + getClass().getName() + ")"; } /** * Gets the name of a TestCase - * @return returns a String + * @return the name of the TestCase */ public String getName() { return fName; } /** * Sets the name of a TestCase - * @param name The name to set + * @param name the name to set */ public void setName(String name) { fName= name; diff --git a/src/junit/framework/TestFailure.java b/src/junit/framework/TestFailure.java index aff6a5a..6662b1f 100644 --- a/src/junit/framework/TestFailure.java +++ b/src/junit/framework/TestFailure.java @@ -36,6 +36,7 @@ public class TestFailure extends Object { /** * Returns a short description of the failure. */ + @Override public String toString() { StringBuffer buffer= new StringBuffer(); buffer.append(fFailedTest+": "+fThrownException.getMessage()); diff --git a/src/junit/framework/TestResult.java b/src/junit/framework/TestResult.java index 8535ce0..5768e9a 100644 --- a/src/junit/framework/TestResult.java +++ b/src/junit/framework/TestResult.java @@ -1,28 +1,30 @@ package junit.framework; +import java.util.ArrayList; +import java.util.Collections; import java.util.Enumeration; -import java.util.Vector; +import java.util.List; /** * A <code>TestResult</code> collects the results of executing * a test case. It is an instance of the Collecting Parameter pattern. * The test framework distinguishes between <i>failures</i> and <i>errors</i>. * A failure is anticipated and checked for with assertions. Errors are - * unanticipated problems like an <code>ArrayIndexOutOfBoundsException</code>. + * unanticipated problems like an {@link ArrayIndexOutOfBoundsException}. * * @see Test */ public class TestResult extends Object { - protected Vector fFailures; - protected Vector fErrors; - protected Vector fListeners; + protected List<TestFailure> fFailures; + protected List<TestFailure> fErrors; + protected List<TestListener> fListeners; protected int fRunTests; private boolean fStop; public TestResult() { - fFailures= new Vector(); - fErrors= new Vector(); - fListeners= new Vector(); + fFailures= new ArrayList<TestFailure>(); + fErrors= new ArrayList<TestFailure>(); + fListeners= new ArrayList<TestListener>(); fRunTests= 0; fStop= false; } @@ -31,46 +33,45 @@ public class TestResult extends Object { * caused the error. */ public synchronized void addError(Test test, Throwable t) { - fErrors.addElement(new TestFailure(test, t)); - for (Enumeration e= cloneListeners().elements(); e.hasMoreElements(); ) { - ((TestListener)e.nextElement()).addError(test, t); - } + fErrors.add(new TestFailure(test, t)); + for (TestListener each : cloneListeners()) + each.addError(test, t); } /** * Adds a failure to the list of failures. The passed in exception * caused the failure. */ public synchronized void addFailure(Test test, AssertionFailedError t) { - fFailures.addElement(new TestFailure(test, t)); - for (Enumeration e= cloneListeners().elements(); e.hasMoreElements(); ) { - ((TestListener)e.nextElement()).addFailure(test, t); - } + fFailures.add(new TestFailure(test, t)); + for (TestListener each : cloneListeners()) + each.addFailure(test, t); } /** * Registers a TestListener */ public synchronized void addListener(TestListener listener) { - fListeners.addElement(listener); + fListeners.add(listener); } /** * Unregisters a TestListener */ public synchronized void removeListener(TestListener listener) { - fListeners.removeElement(listener); + fListeners.remove(listener); } /** * Returns a copy of the listeners. */ - private synchronized Vector cloneListeners() { - return (Vector)fListeners.clone(); + private synchronized List<TestListener> cloneListeners() { + List<TestListener> result= new ArrayList<TestListener>(); + result.addAll(fListeners); + return result; } /** * Informs the result that a test was completed. */ public void endTest(Test test) { - for (Enumeration e= cloneListeners().elements(); e.hasMoreElements(); ) { - ((TestListener)e.nextElement()).endTest(test); - } + for (TestListener each : cloneListeners()) + each.endTest(test); } /** * Gets the number of detected errors. @@ -81,9 +82,11 @@ public class TestResult extends Object { /** * Returns an Enumeration for the errors */ - public synchronized Enumeration errors() { - return fErrors.elements(); + public synchronized Enumeration<TestFailure> errors() { + return Collections.enumeration(fErrors); } + + /** * Gets the number of detected failures. */ @@ -93,9 +96,10 @@ public class TestResult extends Object { /** * Returns an Enumeration for the failures */ - public synchronized Enumeration failures() { - return fFailures.elements(); + public synchronized Enumeration<TestFailure> failures() { + return Collections.enumeration(fFailures); } + /** * Runs a TestCase. */ @@ -147,9 +151,8 @@ public class TestResult extends Object { synchronized(this) { fRunTests+= count; } - for (Enumeration e= cloneListeners().elements(); e.hasMoreElements(); ) { - ((TestListener)e.nextElement()).startTest(test); - } + for (TestListener each : cloneListeners()) + each.startTest(test); } /** * Marks that the test run should stop. diff --git a/src/junit/framework/TestSuite.java b/src/junit/framework/TestSuite.java index 5a96bc2..336efd1 100644 --- a/src/junit/framework/TestSuite.java +++ b/src/junit/framework/TestSuite.java @@ -6,11 +6,13 @@ import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; +import java.util.ArrayList; import java.util.Enumeration; +import java.util.List; import java.util.Vector; /** - * A <code>TestSuite</code> is a <code>Composite</code> of Tests. + * <p>A <code>TestSuite</code> is a <code>Composite</code> of Tests. * It runs a collection of test cases. Here is an example using * the dynamic test definition. * <pre> @@ -18,20 +20,25 @@ import java.util.Vector; * suite.addTest(new MathTest("testAdd")); * suite.addTest(new MathTest("testDivideByZero")); * </pre> - * Alternatively, a TestSuite can extract the tests to be run automatically. + * </p> + * + * <p>Alternatively, a TestSuite can extract the tests to be run automatically. * To do so you pass the class of your TestCase class to the * TestSuite constructor. * <pre> * TestSuite suite= new TestSuite(MathTest.class); * </pre> - * This constructor creates a suite with all the methods - * starting with "test" that take no arguments. - * <p> - * A final option is to do the same for a large array of test classes. + * </p> + * + * <p>This constructor creates a suite with all the methods + * starting with "test" that take no arguments.</p> + * + * <p>A final option is to do the same for a large array of test classes. * <pre> * Class[] testClasses = { MathTest.class, AnotherTest.class } * TestSuite suite= new TestSuite(testClasses); * </pre> + * </p> * * @see Test */ @@ -41,8 +48,8 @@ public class TestSuite implements Test { * ...as the moon sets over the early morning Merlin, Oregon * mountains, our intrepid adventurers type... */ - static public Test createTest(Class theClass, String name) { - Constructor constructor; + static public Test createTest(Class<?> theClass, String name) { + Constructor<?> constructor; try { constructor= getTestConstructor(theClass); } catch (NoSuchMethodException e) { @@ -71,10 +78,9 @@ public class TestSuite implements Test { * Gets a constructor which takes a single String as * its argument or a no arg constructor. */ - public static Constructor getTestConstructor(Class theClass) throws NoSuchMethodException { - Class[] args= { String.class }; + public static Constructor<?> getTestConstructor(Class<?> theClass) throws NoSuchMethodException { try { - return theClass.getConstructor(args); + return theClass.getConstructor(String.class); } catch (NoSuchMethodException e) { // fall through } @@ -86,6 +92,7 @@ public class TestSuite implements Test { */ public static Test warning(final String message) { return new TestCase("warning") { + @Override protected void runTest() { fail(message); } @@ -100,11 +107,11 @@ public class TestSuite implements Test { PrintWriter writer= new PrintWriter(stringWriter); t.printStackTrace(writer); return stringWriter.toString(); - } + private String fName; - private Vector fTests= new Vector(10); + private Vector<Test> fTests= new Vector<Test>(10); // Cannot convert this to List because it is used directly by some test runners /** * Constructs an empty TestSuite. @@ -115,10 +122,14 @@ public class TestSuite implements Test { /** * Constructs a TestSuite from the given class. Adds all the methods * starting with "test" as test cases to the suite. - * Parts of this method was written at 2337 meters in the Hueffihuette, + * Parts of this method were written at 2337 meters in the Hueffihuette, * Kanton Uri */ - public TestSuite(final Class theClass) { + public TestSuite(final Class<?> theClass) { + addTestsFromTestCase(theClass); + } + + private void addTestsFromTestCase(final Class<?> theClass) { fName= theClass.getName(); try { getTestConstructor(theClass); // Avoid generating multiple error messages @@ -132,13 +143,11 @@ public class TestSuite implements Test { return; } - Class superClass= theClass; - Vector names= new Vector(); + Class<?> superClass= theClass; + List<String> names= new ArrayList<String>(); while (Test.class.isAssignableFrom(superClass)) { - Method[] methods= superClass.getDeclaredMethods(); - for (int i= 0; i < methods.length; i++) { - addTestMethod(methods[i], names, theClass); - } + for (Method each : superClass.getDeclaredMethods()) + addTestMethod(each, names, theClass); superClass= superClass.getSuperclass(); } if (fTests.size() == 0) @@ -149,7 +158,7 @@ public class TestSuite implements Test { * Constructs a TestSuite from the given class with the given name. * @see TestSuite#TestSuite(Class) */ - public TestSuite(Class theClass, String name) { + public TestSuite(Class<? extends TestCase> theClass, String name) { this(theClass); setName(name); } @@ -163,18 +172,25 @@ public class TestSuite implements Test { /** * Constructs a TestSuite from the given array of classes. - * @param classes + * @param classes {@link TestCase}s */ - public TestSuite (Class[] classes) { - for (int i= 0; i < classes.length; i++) - addTest(new TestSuite(classes[i])); + public TestSuite (Class<?>... classes) { + for (Class<?> each : classes) + addTest(testCaseForClass(each)); + } + + private Test testCaseForClass(Class<?> each) { + if (TestCase.class.isAssignableFrom(each)) + return new TestSuite(each.asSubclass(TestCase.class)); + else + return warning(each.getCanonicalName() + " does not extend TestCase"); } /** * Constructs a TestSuite from the given array of classes with the given name. * @see TestSuite#TestSuite(Class[]) */ - public TestSuite(Class[] classes, String name) { + public TestSuite(Class<? extends TestCase>[] classes, String name) { this(classes); setName(name); } @@ -183,13 +199,13 @@ public class TestSuite implements Test { * Adds a test to the suite. */ public void addTest(Test test) { - fTests.addElement(test); + fTests.add(test); } /** * Adds the tests from the given class to the suite */ - public void addTestSuite(Class testClass) { + public void addTestSuite(Class<? extends TestCase> testClass) { addTest(new TestSuite(testClass)); } @@ -198,10 +214,8 @@ public class TestSuite implements Test { */ public int countTestCases() { int count= 0; - for (Enumeration e= tests(); e.hasMoreElements(); ) { - Test test= (Test)e.nextElement(); - count= count + test.countTestCases(); - } + for (Test each : fTests) + count+= each.countTestCases(); return count; } @@ -218,11 +232,10 @@ public class TestSuite implements Test { * Runs the tests and collects their result in a TestResult. */ public void run(TestResult result) { - for (Enumeration e= tests(); e.hasMoreElements(); ) { + for (Test each : fTests) { if (result.shouldStop() ) break; - Test test= (Test)e.nextElement(); - runTest(test, result); + runTest(each, result); } } @@ -232,7 +245,7 @@ public class TestSuite implements Test { /** * Sets the name of the suite. - * @param name The name to set + * @param name the name to set */ public void setName(String name) { fName= name; @@ -242,7 +255,7 @@ public class TestSuite implements Test { * Returns the test at the given index */ public Test testAt(int index) { - return (Test)fTests.elementAt(index); + return fTests.get(index); } /** @@ -255,28 +268,29 @@ public class TestSuite implements Test { /** * Returns the tests as an enumeration */ - public Enumeration tests() { + public Enumeration<Test> tests() { return fTests.elements(); } /** */ + @Override public String toString() { if (getName() != null) return getName(); return super.toString(); } - private void addTestMethod(Method m, Vector names, Class theClass) { + private void addTestMethod(Method m, List<String> names, Class<?> theClass) { String name= m.getName(); if (names.contains(name)) return; if (! isPublicTestMethod(m)) { if (isTestMethod(m)) - addTest(warning("Test method isn't public: "+m.getName())); + addTest(warning("Test method isn't public: "+ m.getName() + "(" + theClass.getCanonicalName() + ")")); return; } - names.addElement(name); + names.add(name); addTest(createTest(theClass, name)); } @@ -285,9 +299,9 @@ public class TestSuite implements Test { } private boolean isTestMethod(Method m) { - String name= m.getName(); - Class[] parameters= m.getParameterTypes(); - Class returnType= m.getReturnType(); - return parameters.length == 0 && name.startsWith("test") && returnType.equals(Void.TYPE); + return + m.getParameterTypes().length == 0 && + m.getName().startsWith("test") && + m.getReturnType().equals(Void.TYPE); } }
\ No newline at end of file diff --git a/src/junit/framework/package-info.java b/src/junit/framework/package-info.java new file mode 100644 index 0000000..153a1c8 --- /dev/null +++ b/src/junit/framework/package-info.java @@ -0,0 +1,4 @@ +/** + * Provides JUnit v3.x core classes. + */ +package junit.framework;
\ No newline at end of file diff --git a/src/junit/runner/BaseTestRunner.java b/src/junit/runner/BaseTestRunner.java index 1518f7c..6a4b090 100644 --- a/src/junit/runner/BaseTestRunner.java +++ b/src/junit/runner/BaseTestRunner.java @@ -56,8 +56,7 @@ public abstract class BaseTestRunner implements TestListener { public static void savePreferences() throws IOException { FileOutputStream fos= new FileOutputStream(getPreferencesFile()); try { - // calling of the deprecated save method to enable compiling under 1.1.7 - getPreferences().save(fos, ""); + getPreferences().store(fos, ""); } finally { fos.close(); } @@ -96,7 +95,7 @@ public abstract class BaseTestRunner implements TestListener { clearStatus(); return null; } - Class testClass= null; + Class<?> testClass= null; try { testClass= loadSuiteClass(suiteClassName); } catch (ClassNotFoundException e) { @@ -204,8 +203,8 @@ public abstract class BaseTestRunner implements TestListener { /** * Returns the loaded Class for a suite name. */ - protected Class loadSuiteClass(String suiteClassName) throws ClassNotFoundException { - return getLoader().load(suiteClassName); + protected Class<?> loadSuiteClass(String suiteClassName) throws ClassNotFoundException { + return Class.forName(suiteClassName); } /** @@ -214,17 +213,8 @@ public abstract class BaseTestRunner implements TestListener { protected void clearStatus() { // Belongs in the GUI TestRunner class } - /** - * Returns the loader to be used. - */ - public TestSuiteLoader getLoader() { - if (useReloadingTestSuiteLoader()) - return new ReloadingTestSuiteLoader(); - return new StandardTestSuiteLoader(); - } - protected boolean useReloadingTestSuiteLoader() { - return getPreference("loading").equals("true") && !inVAJava() && fLoading; + return getPreference("loading").equals("true") && fLoading; } private static File getPreferencesFile() { @@ -263,21 +253,6 @@ public abstract class BaseTestRunner implements TestListener { return intValue; } - public static boolean inVAJava() { - try { - Class.forName("com.ibm.uvm.tools.DebugSupport"); - } - catch (Exception e) { - return false; - } - return true; - } - - public static boolean inMac() { - return System.getProperty("mrj.version") != null; - } - - /** * Returns a filtered stack trace */ diff --git a/src/junit/runner/ClassPathTestCollector.java b/src/junit/runner/ClassPathTestCollector.java deleted file mode 100644 index 0dbb98e..0000000 --- a/src/junit/runner/ClassPathTestCollector.java +++ /dev/null @@ -1,83 +0,0 @@ -package junit.runner; - -import java.io.File; -import java.util.Enumeration; -import java.util.Hashtable; -import java.util.StringTokenizer; -import java.util.Vector; - -/** - * An implementation of a TestCollector that consults the - * class path. It considers all classes on the class path - * excluding classes in JARs. It leaves it up to subclasses - * to decide whether a class is a runnable Test. - * - * @see TestCollector - */ -public abstract class ClassPathTestCollector implements TestCollector { - - static final int SUFFIX_LENGTH= ".class".length(); - - public ClassPathTestCollector() { - } - - public Enumeration collectTests() { - String classPath= System.getProperty("java.class.path"); - Hashtable result = collectFilesInPath(classPath); - return result.elements(); - } - - public Hashtable collectFilesInPath(String classPath) { - Hashtable result= collectFilesInRoots(splitClassPath(classPath)); - return result; - } - - Hashtable collectFilesInRoots(Vector roots) { - Hashtable result= new Hashtable(100); - Enumeration e= roots.elements(); - while (e.hasMoreElements()) - gatherFiles(new File((String)e.nextElement()), "", result); - return result; - } - - void gatherFiles(File classRoot, String classFileName, Hashtable result) { - File thisRoot= new File(classRoot, classFileName); - if (thisRoot.isFile()) { - if (isTestClass(classFileName)) { - String className= classNameFromFile(classFileName); - result.put(className, className); - } - return; - } - String[] contents= thisRoot.list(); - if (contents != null) { - for (int i= 0; i < contents.length; i++) - gatherFiles(classRoot, classFileName+File.separatorChar+contents[i], result); - } - } - - Vector splitClassPath(String classPath) { - Vector result= new Vector(); - String separator= System.getProperty("path.separator"); - StringTokenizer tokenizer= new StringTokenizer(classPath, separator); - while (tokenizer.hasMoreTokens()) - result.addElement(tokenizer.nextToken()); - return result; - } - - protected boolean isTestClass(String classFileName) { - return - classFileName.endsWith(".class") && - classFileName.indexOf('$') < 0 && - classFileName.indexOf("Test") > 0; - } - - protected String classNameFromFile(String classFileName) { - // convert /a/b.class to a.b - String s= classFileName.substring(0, classFileName.length()-SUFFIX_LENGTH); - String s2= s.replace(File.separatorChar, '.'); - if (s2.startsWith(".")) - return s2.substring(1); - return s2; - } -} diff --git a/src/junit/runner/FailureDetailView.java b/src/junit/runner/FailureDetailView.java deleted file mode 100644 index fc9aaf4..0000000 --- a/src/junit/runner/FailureDetailView.java +++ /dev/null @@ -1,23 +0,0 @@ -package junit.runner; - -import java.awt.Component; - -import junit.framework.TestFailure; - -/** - * A view to show a details about a failure - */ -public interface FailureDetailView { - /** - * Returns the component used to present the TraceView - */ - public Component getComponent(); - /** - * Shows details of a TestFailure - */ - public void showFailure(TestFailure failure); - /** - * Clears the view - */ - public void clear(); -}
\ No newline at end of file diff --git a/src/junit/runner/LoadingTestCollector.java b/src/junit/runner/LoadingTestCollector.java deleted file mode 100644 index 21a3144..0000000 --- a/src/junit/runner/LoadingTestCollector.java +++ /dev/null @@ -1,70 +0,0 @@ -package junit.runner; - -import java.lang.reflect.Modifier; - -import junit.framework.Test; -import junit.framework.TestSuite; - -/** - * An implementation of a TestCollector that loads - * all classes on the class path and tests whether - * it is assignable from Test or provides a static suite method. - * @see TestCollector - */ -public class LoadingTestCollector extends ClassPathTestCollector { - - TestCaseClassLoader fLoader; - - public LoadingTestCollector() { - fLoader= new TestCaseClassLoader(); - } - - protected boolean isTestClass(String classFileName) { - try { - if (classFileName.endsWith(".class")) { - Class testClass= classFromFile(classFileName); - return (testClass != null) && isTestClass(testClass); - } - } - catch (ClassNotFoundException expected) { - } - catch (NoClassDefFoundError notFatal) { - } - return false; - } - - Class classFromFile(String classFileName) throws ClassNotFoundException { - String className= classNameFromFile(classFileName); - if (!fLoader.isExcluded(className)) - return fLoader.loadClass(className, false); - return null; - } - - boolean isTestClass(Class testClass) { - if (hasSuiteMethod(testClass)) - return true; - if (Test.class.isAssignableFrom(testClass) && - Modifier.isPublic(testClass.getModifiers()) && - hasPublicConstructor(testClass)) - return true; - return false; - } - - boolean hasSuiteMethod(Class testClass) { - try { - testClass.getMethod(BaseTestRunner.SUITE_METHODNAME, new Class[0]); - } catch(Exception e) { - return false; - } - return true; - } - - boolean hasPublicConstructor(Class testClass) { - try { - TestSuite.getTestConstructor(testClass); - } catch(NoSuchMethodException e) { - return false; - } - return true; - } -} diff --git a/src/junit/runner/ReloadingTestSuiteLoader.java b/src/junit/runner/ReloadingTestSuiteLoader.java deleted file mode 100644 index 537504b..0000000 --- a/src/junit/runner/ReloadingTestSuiteLoader.java +++ /dev/null @@ -1,19 +0,0 @@ -package junit.runner; - -/** - * A TestSuite loader that can reload classes. - */ -public class ReloadingTestSuiteLoader implements TestSuiteLoader { - - public Class load(String suiteClassName) throws ClassNotFoundException { - return createLoader().loadClass(suiteClassName, true); - } - - public Class reload(Class aClass) throws ClassNotFoundException { - return createLoader().loadClass(aClass.getName(), true); - } - - protected TestCaseClassLoader createLoader() { - return new TestCaseClassLoader(); - } -}
\ No newline at end of file diff --git a/src/junit/runner/SimpleTestCollector.java b/src/junit/runner/SimpleTestCollector.java deleted file mode 100644 index 9d1956a..0000000 --- a/src/junit/runner/SimpleTestCollector.java +++ /dev/null @@ -1,20 +0,0 @@ -package junit.runner; - -/** - * An implementation of a TestCollector that considers - * a class to be a test class when it contains the - * pattern "Test" in its name - * @see TestCollector - */ -public class SimpleTestCollector extends ClassPathTestCollector { - - public SimpleTestCollector() { - } - - protected boolean isTestClass(String classFileName) { - return - classFileName.endsWith(".class") && - classFileName.indexOf('$') < 0 && - classFileName.indexOf("Test") > 0; - } -} diff --git a/src/junit/runner/Sorter.java b/src/junit/runner/Sorter.java deleted file mode 100644 index e868992..0000000 --- a/src/junit/runner/Sorter.java +++ /dev/null @@ -1,36 +0,0 @@ -package junit.runner; - -import java.util.Vector; - -/** - * A custom quick sort with support to customize the swap behaviour. - * NOTICE: We can't use the the sorting support from the JDK 1.2 collection - * classes because of the JDK 1.1.7 compatibility. - */ -public class Sorter { - public static interface Swapper { - public void swap(Vector values, int left, int right); - } - - public static void sortStrings(Vector values , int left, int right, Swapper swapper) { - int oleft= left; - int oright= right; - String mid= (String)values.elementAt((left + right) / 2); - do { - while (((String)(values.elementAt(left))).compareTo(mid) < 0) - left++; - while (mid.compareTo((String)(values.elementAt(right))) < 0) - right--; - if (left <= right) { - swapper.swap(values, left, right); - left++; - right--; - } - } while (left <= right); - - if (oleft < right) - sortStrings(values, oleft, right, swapper); - if (left < oright) - sortStrings(values, left, oright, swapper); - } -}
\ No newline at end of file diff --git a/src/junit/runner/StandardTestSuiteLoader.java b/src/junit/runner/StandardTestSuiteLoader.java deleted file mode 100644 index 54f29c1..0000000 --- a/src/junit/runner/StandardTestSuiteLoader.java +++ /dev/null @@ -1,19 +0,0 @@ -package junit.runner; - -/** - * The standard test suite loader. It can only load the same class once. - */ -public class StandardTestSuiteLoader implements TestSuiteLoader { - /** - * Uses the system class loader to load the test class - */ - public Class load(String suiteClassName) throws ClassNotFoundException { - return Class.forName(suiteClassName); - } - /** - * Uses the system class loader to load the test class - */ - public Class reload(Class aClass) throws ClassNotFoundException { - return aClass; - } -}
\ No newline at end of file diff --git a/src/junit/runner/TestCaseClassLoader.java b/src/junit/runner/TestCaseClassLoader.java deleted file mode 100644 index b4bbc24..0000000 --- a/src/junit/runner/TestCaseClassLoader.java +++ /dev/null @@ -1,240 +0,0 @@ -package junit.runner; - -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.net.URL; -import java.util.Enumeration; -import java.util.Properties; -import java.util.StringTokenizer; -import java.util.Vector; -import java.util.zip.ZipEntry; -import java.util.zip.ZipFile; - -/** - * A custom class loader which enables the reloading - * of classes for each test run. The class loader - * can be configured with a list of package paths that - * should be excluded from loading. The loading - * of these packages is delegated to the system class - * loader. They will be shared across test runs. - * <p> - * The list of excluded package paths is specified in - * a properties file "excluded.properties" that is located in - * the same place as the TestCaseClassLoader class. - * <p> - * <b>Known limitation:</b> the TestCaseClassLoader cannot load classes - * from jar files. - */ - - -public class TestCaseClassLoader extends ClassLoader { - /** scanned class path */ - private Vector fPathItems; - /** default excluded paths */ - private String[] defaultExclusions= { - "junit.framework.", - "junit.extensions.", - "junit.runner." - }; - /** name of excluded properties file */ - static final String EXCLUDED_FILE= "excluded.properties"; - /** excluded paths */ - private Vector fExcluded; - - /** - * Constructs a TestCaseLoader. It scans the class path - * and the excluded package paths - */ - public TestCaseClassLoader() { - this(System.getProperty("java.class.path")); - } - - /** - * Constructs a TestCaseLoader. It scans the class path - * and the excluded package paths - */ - public TestCaseClassLoader(String classPath) { - scanPath(classPath); - readExcludedPackages(); - } - - private void scanPath(String classPath) { - String separator= System.getProperty("path.separator"); - fPathItems= new Vector(10); - StringTokenizer st= new StringTokenizer(classPath, separator); - while (st.hasMoreTokens()) { - fPathItems.addElement(st.nextToken()); - } - } - - public URL getResource(String name) { - return ClassLoader.getSystemResource(name); - } - - public InputStream getResourceAsStream(String name) { - return ClassLoader.getSystemResourceAsStream(name); - } - - public boolean isExcluded(String name) { - for (int i= 0; i < fExcluded.size(); i++) { - if (name.startsWith((String) fExcluded.elementAt(i))) { - return true; - } - } - return false; - } - - public synchronized Class loadClass(String name, boolean resolve) - throws ClassNotFoundException { - - Class c= findLoadedClass(name); - if (c != null) - return c; - // - // Delegate the loading of excluded classes to the - // standard class loader. - // - if (isExcluded(name)) { - try { - c= findSystemClass(name); - return c; - } catch (ClassNotFoundException e) { - // keep searching - } - } - if (c == null) { - byte[] data= lookupClassData(name); - if (data == null) - throw new ClassNotFoundException(); - c= defineClass(name, data, 0, data.length); - } - if (resolve) - resolveClass(c); - return c; - } - - private byte[] lookupClassData(String className) throws ClassNotFoundException { - byte[] data= null; - for (int i= 0; i < fPathItems.size(); i++) { - String path= (String) fPathItems.elementAt(i); - String fileName= className.replace('.', '/')+".class"; - if (isJar(path)) { - data= loadJarData(path, fileName); - } else { - data= loadFileData(path, fileName); - } - if (data != null) - return data; - } - throw new ClassNotFoundException(className); - } - - boolean isJar(String pathEntry) { - return pathEntry.endsWith(".jar") || pathEntry.endsWith(".zip"); - } - - private byte[] loadFileData(String path, String fileName) { - File file= new File(path, fileName); - if (file.exists()) { - return getClassData(file); - } - return null; - } - - private byte[] getClassData(File f) { - FileInputStream stream= null; - try { - stream= new FileInputStream(f); - ByteArrayOutputStream out= new ByteArrayOutputStream(1000); - byte[] b= new byte[1000]; - int n; - while ((n= stream.read(b)) != -1) - out.write(b, 0, n); - stream.close(); - out.close(); - return out.toByteArray(); - - } catch (IOException e) { - } - finally { - if (stream != null) - try { - stream.close(); - } catch (IOException e1) { - } - } - return null; - } - - private byte[] loadJarData(String path, String fileName) { - ZipFile zipFile= null; - InputStream stream= null; - File archive= new File(path); - if (!archive.exists()) - return null; - try { - zipFile= new ZipFile(archive); - } catch(IOException io) { - return null; - } - ZipEntry entry= zipFile.getEntry(fileName); - if (entry == null) - return null; - int size= (int) entry.getSize(); - try { - stream= zipFile.getInputStream(entry); - byte[] data= new byte[size]; - int pos= 0; - while (pos < size) { - int n= stream.read(data, pos, data.length - pos); - pos += n; - } - zipFile.close(); - return data; - } catch (IOException e) { - } finally { - try { - if (stream != null) - stream.close(); - } catch (IOException e) { - } - } - return null; - } - - private void readExcludedPackages() { - fExcluded= new Vector(10); - for (int i= 0; i < defaultExclusions.length; i++) - fExcluded.addElement(defaultExclusions[i]); - - InputStream is= getClass().getResourceAsStream(EXCLUDED_FILE); - if (is == null) - return; - Properties p= new Properties(); - try { - p.load(is); - } - catch (IOException e) { - return; - } finally { - try { - is.close(); - } catch (IOException e) { - } - } - for (Enumeration e= p.propertyNames(); e.hasMoreElements(); ) { - String key= (String)e.nextElement(); - if (key.startsWith("excluded.")) { - String path= p.getProperty(key); - path= path.trim(); - if (path.endsWith("*")) - path= path.substring(0, path.length()-1); - if (path.length() > 0) - fExcluded.addElement(path); - } - } - } -}
\ No newline at end of file diff --git a/src/junit/runner/TestCollector.java b/src/junit/runner/TestCollector.java deleted file mode 100644 index 276e7fa..0000000 --- a/src/junit/runner/TestCollector.java +++ /dev/null @@ -1,17 +0,0 @@ -package junit.runner; - -import java.util.Enumeration; -import junit.swingui.TestSelector; - - -/** - * Collects Test class names to be presented - * by the TestSelector. - * @see TestSelector - */ -public interface TestCollector { - /** - * Returns an enumeration of Strings with qualified class names - */ - public Enumeration collectTests(); -} diff --git a/src/junit/runner/TestSuiteLoader.java b/src/junit/runner/TestSuiteLoader.java deleted file mode 100644 index 2db589e..0000000 --- a/src/junit/runner/TestSuiteLoader.java +++ /dev/null @@ -1,9 +0,0 @@ -package junit.runner; - -/** - * An interface to define how a test suite should be loaded. - */ -public interface TestSuiteLoader { - abstract public Class load(String suiteClassName) throws ClassNotFoundException; - abstract public Class reload(Class aClass) throws ClassNotFoundException; -}
\ No newline at end of file diff --git a/src/junit/runner/Version.java b/src/junit/runner/Version.java index 7fd76aa..eb4794b 100644 --- a/src/junit/runner/Version.java +++ b/src/junit/runner/Version.java @@ -9,7 +9,7 @@ public class Version { } public static String id() { - return "3.8.2"; + return "4.10"; } public static void main(String[] args) { diff --git a/src/junit/runner/Version.java.template b/src/junit/runner/Version.java.template new file mode 100644 index 0000000..3182cfd --- /dev/null +++ b/src/junit/runner/Version.java.template @@ -0,0 +1,18 @@ +package junit.runner; + +/** + * This class defines the current version of JUnit + */ +public class Version { + private Version() { + // don't instantiate + } + + public static String id() { + return "@version@"; + } + + public static void main(String[] args) { + System.out.println(id()); + } +} diff --git a/src/junit/runner/excluded.properties b/src/junit/runner/excluded.properties deleted file mode 100644 index 011ae3c..0000000 --- a/src/junit/runner/excluded.properties +++ /dev/null @@ -1,13 +0,0 @@ -# -# The list of excluded package paths for the TestCaseClassLoader -# -excluded.0=sun.* -excluded.1=com.sun.* -excluded.2=org.omg.* -excluded.3=javax.* -excluded.4=sunw.* -excluded.5=java.* -excluded.6=org.w3c.dom.* -excluded.7=org.xml.sax.* -excluded.8=net.jini.* -excluded.9=org.apache.commons.logging.*
\ No newline at end of file diff --git a/src/junit/runner/package-info.java b/src/junit/runner/package-info.java new file mode 100644 index 0000000..b746185 --- /dev/null +++ b/src/junit/runner/package-info.java @@ -0,0 +1,4 @@ +/** + * Provides JUnit v3.x test runners. + */ +package junit.runner;
\ No newline at end of file diff --git a/src/junit/swingui/AboutDialog.java b/src/junit/swingui/AboutDialog.java deleted file mode 100644 index c55b420..0000000 --- a/src/junit/swingui/AboutDialog.java +++ /dev/null @@ -1,93 +0,0 @@ -package junit.swingui; - -import java.awt.Font; -import java.awt.GridBagConstraints; -import java.awt.GridBagLayout; -import java.awt.Insets; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.awt.event.WindowAdapter; -import java.awt.event.WindowEvent; - -import javax.swing.Icon; -import javax.swing.JButton; -import javax.swing.JDialog; -import javax.swing.JFrame; -import javax.swing.JLabel; - -import junit.runner.BaseTestRunner; -import junit.runner.Version; - -/** - * The AboutDialog. - */ -class AboutDialog extends JDialog { - public AboutDialog(JFrame parent) { - super(parent, true); - - setResizable(false); - getContentPane().setLayout(new GridBagLayout()); - setSize(330, 138); - setTitle("About"); - // setLocationRelativeTo is only available in JDK 1.4 - try { - setLocationRelativeTo(parent); - } catch (NoSuchMethodError e) { - TestSelector.centerWindow(this); - } - - JButton close= new JButton("Close"); - close.addActionListener( - new ActionListener() { - public void actionPerformed(ActionEvent e) { - dispose(); - } - } - ); - getRootPane().setDefaultButton(close); - JLabel label1= new JLabel("JUnit"); - label1.setFont(new Font("dialog", Font.PLAIN, 36)); - - JLabel label2= new JLabel("JUnit "+Version.id()+" by Kent Beck and Erich Gamma"); - label2.setFont(new Font("dialog", Font.PLAIN, 14)); - - JLabel logo= createLogo(); - - GridBagConstraints constraintsLabel1= new GridBagConstraints(); - constraintsLabel1.gridx = 3; constraintsLabel1.gridy = 0; - constraintsLabel1.gridwidth = 1; constraintsLabel1.gridheight = 1; - constraintsLabel1.anchor = GridBagConstraints.CENTER; - getContentPane().add(label1, constraintsLabel1); - - GridBagConstraints constraintsLabel2= new GridBagConstraints(); - constraintsLabel2.gridx = 2; constraintsLabel2.gridy = 1; - constraintsLabel2.gridwidth = 2; constraintsLabel2.gridheight = 1; - constraintsLabel2.anchor = GridBagConstraints.CENTER; - getContentPane().add(label2, constraintsLabel2); - - GridBagConstraints constraintsButton1= new GridBagConstraints(); - constraintsButton1.gridx = 2; constraintsButton1.gridy = 2; - constraintsButton1.gridwidth = 2; constraintsButton1.gridheight = 1; - constraintsButton1.anchor = GridBagConstraints.CENTER; - constraintsButton1.insets= new Insets(8, 0, 8, 0); - getContentPane().add(close, constraintsButton1); - - GridBagConstraints constraintsLogo1= new GridBagConstraints(); - constraintsLogo1.gridx = 2; constraintsLogo1.gridy = 0; - constraintsLogo1.gridwidth = 1; constraintsLogo1.gridheight = 1; - constraintsLogo1.anchor = GridBagConstraints.CENTER; - getContentPane().add(logo, constraintsLogo1); - - addWindowListener( - new WindowAdapter() { - public void windowClosing(WindowEvent e) { - dispose(); - } - } - ); - } - protected JLabel createLogo() { - Icon icon= TestRunner.getIconResource(BaseTestRunner.class, "logo.gif"); - return new JLabel(icon); - } -}
\ No newline at end of file diff --git a/src/junit/swingui/CounterPanel.java b/src/junit/swingui/CounterPanel.java deleted file mode 100644 index cac4427..0000000 --- a/src/junit/swingui/CounterPanel.java +++ /dev/null @@ -1,118 +0,0 @@ -package junit.swingui; - -import java.awt.Component; -import java.awt.GridBagConstraints; -import java.awt.GridBagLayout; -import java.awt.Insets; - -import javax.swing.BorderFactory; -import javax.swing.Icon; -import javax.swing.JLabel; -import javax.swing.JPanel; -import javax.swing.JTextField; -import javax.swing.SwingConstants; - -/** - * A panel with test run counters - */ -public class CounterPanel extends JPanel { - private JTextField fNumberOfErrors; - private JTextField fNumberOfFailures; - private JTextField fNumberOfRuns; - private Icon fFailureIcon= TestRunner.getIconResource(getClass(), "icons/failure.gif"); - private Icon fErrorIcon= TestRunner.getIconResource(getClass(), "icons/error.gif"); - - private int fTotal; - - public CounterPanel() { - super(new GridBagLayout()); - fNumberOfErrors= createOutputField(5); - fNumberOfFailures= createOutputField(5); - fNumberOfRuns= createOutputField(9); - - addToGrid(new JLabel("Runs:", SwingConstants.CENTER), - 0, 0, 1, 1, 0.0, 0.0, - GridBagConstraints.CENTER, GridBagConstraints.NONE, - new Insets(0, 0, 0, 0)); - addToGrid(fNumberOfRuns, - 1, 0, 1, 1, 0.33, 0.0, - GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, - new Insets(0, 8, 0, 0)); - - addToGrid(new JLabel("Errors:", fErrorIcon, SwingConstants.LEFT), - 2, 0, 1, 1, 0.0, 0.0, - GridBagConstraints.CENTER, GridBagConstraints.NONE, - new Insets(0, 8, 0, 0)); - addToGrid(fNumberOfErrors, - 3, 0, 1, 1, 0.33, 0.0, - GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, - new Insets(0, 8, 0, 0)); - - addToGrid(new JLabel("Failures:", fFailureIcon, SwingConstants.LEFT), - 4, 0, 1, 1, 0.0, 0.0, - GridBagConstraints.CENTER, GridBagConstraints.NONE, - new Insets(0, 8, 0, 0)); - addToGrid(fNumberOfFailures, - 5, 0, 1, 1, 0.33, 0.0, - GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, - new Insets(0, 8, 0, 0)); - } - - private JTextField createOutputField(int width) { - JTextField field= new JTextField("0", width); - // force a fixed layout to avoid accidental hiding on relayout - field.setMinimumSize(field.getPreferredSize()); - field.setMaximumSize(field.getPreferredSize()); - field.setHorizontalAlignment(SwingConstants.LEFT); - field.setFont(StatusLine.BOLD_FONT); - field.setEditable(false); - field.setBorder(BorderFactory.createEmptyBorder()); - return field; - } - - public void addToGrid(Component comp, - int gridx, int gridy, int gridwidth, int gridheight, - double weightx, double weighty, - int anchor, int fill, - Insets insets) { - - GridBagConstraints constraints= new GridBagConstraints(); - constraints.gridx= gridx; - constraints.gridy= gridy; - constraints.gridwidth= gridwidth; - constraints.gridheight= gridheight; - constraints.weightx= weightx; - constraints.weighty= weighty; - constraints.anchor= anchor; - constraints.fill= fill; - constraints.insets= insets; - add(comp, constraints); - } - - public void reset() { - setLabelValue(fNumberOfErrors, 0); - setLabelValue(fNumberOfFailures, 0); - setLabelValue(fNumberOfRuns, 0); - fTotal= 0; - } - - public void setTotal(int value) { - fTotal= value; - } - - public void setRunValue(int value) { - fNumberOfRuns.setText(Integer.toString(value) + "/" + fTotal); - } - - public void setErrorValue(int value) { - setLabelValue(fNumberOfErrors, value); - } - - public void setFailureValue(int value) { - setLabelValue(fNumberOfFailures, value); - } - - private void setLabelValue(JTextField label, int value) { - label.setText(Integer.toString(value)); - } -}
\ No newline at end of file diff --git a/src/junit/swingui/DefaultFailureDetailView.java b/src/junit/swingui/DefaultFailureDetailView.java deleted file mode 100644 index 51e79c7..0000000 --- a/src/junit/swingui/DefaultFailureDetailView.java +++ /dev/null @@ -1,101 +0,0 @@ -package junit.swingui; - -import java.awt.Component; -import java.awt.Font; -import java.util.StringTokenizer; -import java.util.Vector; - -import javax.swing.AbstractListModel; -import javax.swing.DefaultListCellRenderer; -import javax.swing.JList; -import javax.swing.ListSelectionModel; - -import junit.framework.TestFailure; -import junit.runner.BaseTestRunner; -import junit.runner.FailureDetailView; - -/** - * A view that shows a stack trace of a failure - */ -public class DefaultFailureDetailView implements FailureDetailView { - JList fList; - - /** - * A ListModel representing the scanned failure stack trace. - */ - static class StackTraceListModel extends AbstractListModel { - private Vector fLines= new Vector(20); - - public Object getElementAt(int index) { - return fLines.elementAt(index); - } - - public int getSize() { - return fLines.size(); - } - - public void setTrace(String trace) { - scan(trace); - fireContentsChanged(this, 0, fLines.size()); - } - - public void clear() { - fLines.removeAllElements(); - fireContentsChanged(this, 0, fLines.size()); - } - - private void scan(String trace) { - fLines.removeAllElements(); - StringTokenizer st= new StringTokenizer(trace, "\n\r", false); - while (st.hasMoreTokens()) - fLines.addElement(st.nextToken()); - } - } - - /** - * Renderer for stack entries - */ - static class StackEntryRenderer extends DefaultListCellRenderer { - - public Component getListCellRendererComponent( - JList list, Object value, int modelIndex, - boolean isSelected, boolean cellHasFocus) { - String text= ((String)value).replace('\t', ' '); - Component c= super.getListCellRendererComponent(list, text, modelIndex, isSelected, cellHasFocus); - setText(text); - setToolTipText(text); - return c; - } - } - - /** - * Returns the component used to present the trace - */ - public Component getComponent() { - if (fList == null) { - fList= new JList(new StackTraceListModel()); - fList.setFont(new Font("Dialog", Font.PLAIN, 12)); - fList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); - fList.setVisibleRowCount(5); - fList.setCellRenderer(new StackEntryRenderer()); - } - return fList; - } - - /** - * Shows a TestFailure - */ - public void showFailure(TestFailure failure) { - getModel().setTrace(BaseTestRunner.getFilteredTrace(failure.trace())); - } - /** - * Clears the output. - */ - public void clear() { - getModel().clear(); - } - - private StackTraceListModel getModel() { - return (StackTraceListModel)fList.getModel(); - } -}
\ No newline at end of file diff --git a/src/junit/swingui/FailureRunView.java b/src/junit/swingui/FailureRunView.java deleted file mode 100644 index 3ec6126..0000000 --- a/src/junit/swingui/FailureRunView.java +++ /dev/null @@ -1,122 +0,0 @@ -package junit.swingui; - -import java.awt.Component; -import java.awt.Font; - -import javax.swing.DefaultListCellRenderer; -import javax.swing.Icon; -import javax.swing.JList; -import javax.swing.JScrollPane; -import javax.swing.JTabbedPane; -import javax.swing.ListModel; -import javax.swing.ListSelectionModel; -import javax.swing.ScrollPaneConstants; -import javax.swing.event.ListSelectionEvent; -import javax.swing.event.ListSelectionListener; - -import junit.framework.Test; -import junit.framework.TestFailure; -import junit.framework.TestResult; -import junit.runner.BaseTestRunner; - - -/** - * A view presenting the test failures as a list. - */ -public class FailureRunView implements TestRunView { - JList fFailureList; - TestRunContext fRunContext; - - /** - * Renders TestFailures in a JList - */ - static class FailureListCellRenderer extends DefaultListCellRenderer { - private Icon fFailureIcon; - private Icon fErrorIcon; - - FailureListCellRenderer() { - super(); - loadIcons(); - } - - void loadIcons() { - fFailureIcon= TestRunner.getIconResource(getClass(), "icons/failure.gif"); - fErrorIcon= TestRunner.getIconResource(getClass(), "icons/error.gif"); - } - - public Component getListCellRendererComponent( - JList list, Object value, int modelIndex, - boolean isSelected, boolean cellHasFocus) { - - Component c= super.getListCellRendererComponent(list, value, modelIndex, isSelected, cellHasFocus); - TestFailure failure= (TestFailure)value; - String text= failure.failedTest().toString(); - String msg= failure.exceptionMessage(); - if (msg != null) - text+= ":" + BaseTestRunner.truncate(msg); - - if (failure.isFailure()) { - if (fFailureIcon != null) - setIcon(fFailureIcon); - } else { - if (fErrorIcon != null) - setIcon(fErrorIcon); - } - setText(text); - setToolTipText(text); - return c; - } - } - - public FailureRunView(TestRunContext context) { - fRunContext= context; - fFailureList= new JList(fRunContext.getFailures()); - fFailureList.setFont(new Font("Dialog", Font.PLAIN, 12)); - - fFailureList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); - fFailureList.setCellRenderer(new FailureListCellRenderer()); - fFailureList.setVisibleRowCount(5); - - fFailureList.addListSelectionListener( - new ListSelectionListener() { - public void valueChanged(ListSelectionEvent e) { - testSelected(); - } - } - ); - } - - public Test getSelectedTest() { - int index= fFailureList.getSelectedIndex(); - if (index == -1) - return null; - - ListModel model= fFailureList.getModel(); - TestFailure failure= (TestFailure)model.getElementAt(index); - return failure.failedTest(); - } - - public void activate() { - testSelected(); - } - - public void addTab(JTabbedPane pane) { - JScrollPane scrollPane= new JScrollPane(fFailureList, ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS, ScrollPaneConstants.HORIZONTAL_SCROLLBAR_ALWAYS); - Icon errorIcon= TestRunner.getIconResource(getClass(), "icons/error.gif"); - pane.addTab("Failures", errorIcon, scrollPane, "The list of failed tests"); - } - - public void revealFailure(Test failure) { - fFailureList.setSelectedIndex(0); - } - - public void aboutToStart(Test suite, TestResult result) { - } - - public void runFinished(Test suite, TestResult result) { - } - - protected void testSelected() { - fRunContext.handleTestSelected(getSelectedTest()); - } -} diff --git a/src/junit/swingui/MacProgressBar.java b/src/junit/swingui/MacProgressBar.java deleted file mode 100644 index 1de6cfd..0000000 --- a/src/junit/swingui/MacProgressBar.java +++ /dev/null @@ -1,20 +0,0 @@ -package junit.swingui; - -import javax.swing.JTextField; - -/** - http://java.sun.com/developer/technicalArticles/JavaLP/JavaToMac2/ -*/ -public class MacProgressBar extends ProgressBar { - - private JTextField component; - - public MacProgressBar(JTextField component) { - super(); - this.component= component; - } - - protected void updateBarColor() { - component.setBackground(getStatusColor()); - } -} diff --git a/src/junit/swingui/ProgressBar.java b/src/junit/swingui/ProgressBar.java deleted file mode 100644 index d5de71e..0000000 --- a/src/junit/swingui/ProgressBar.java +++ /dev/null @@ -1,46 +0,0 @@ -package junit.swingui; - -import java.awt.Color; - -import javax.swing.JProgressBar; - -/** - * A progress bar showing the green/red status - */ -class ProgressBar extends JProgressBar { - boolean fError= false; - - public ProgressBar() { - super(); - setForeground(getStatusColor()); - } - - protected Color getStatusColor() { - if (fError) - return Color.red; - return Color.green; - } - - public void reset() { - fError= false; - updateBarColor(); - setValue(0); - } - - public void start(int total) { - setMaximum(total); - reset(); - } - - public void step(int value, boolean successful) { - setValue(value); - if (!fError && !successful) { - fError= true; - updateBarColor(); - } - } - - protected void updateBarColor() { - setForeground(getStatusColor()); - } -} diff --git a/src/junit/swingui/StatusLine.java b/src/junit/swingui/StatusLine.java deleted file mode 100644 index e18fda2..0000000 --- a/src/junit/swingui/StatusLine.java +++ /dev/null @@ -1,45 +0,0 @@ -package junit.swingui; - -import java.awt.Color; -import java.awt.Dimension; -import java.awt.Font; - -import javax.swing.BorderFactory; -import javax.swing.JTextField; -import javax.swing.border.BevelBorder; - -/** - * A status line component. - */ -public class StatusLine extends JTextField { - public static final Font PLAIN_FONT= new Font("dialog", Font.PLAIN, 12); - public static final Font BOLD_FONT= new Font("dialog", Font.BOLD, 12); - - public StatusLine(int preferredWidth) { - super(); - setFont(BOLD_FONT); - setEditable(false); - setBorder(BorderFactory.createBevelBorder(BevelBorder.LOWERED)); - Dimension d= getPreferredSize(); - d.width= preferredWidth; - setPreferredSize(d); - } - - public void showInfo(String message) { - setFont(PLAIN_FONT); - setForeground(Color.black); - setText(message); - } - - public void showError(String status) { - setFont(BOLD_FONT); - setForeground(Color.red); - setText(status); - setToolTipText(status); - } - - public void clear() { - setText(""); - setToolTipText(null); - } -}
\ No newline at end of file diff --git a/src/junit/swingui/TestHierarchyRunView.java b/src/junit/swingui/TestHierarchyRunView.java deleted file mode 100644 index 89bd297..0000000 --- a/src/junit/swingui/TestHierarchyRunView.java +++ /dev/null @@ -1,77 +0,0 @@ -package junit.swingui; - -import java.util.Vector; - -import javax.swing.Icon; -import javax.swing.JTabbedPane; -import javax.swing.JTree; -import javax.swing.event.TreeSelectionEvent; -import javax.swing.event.TreeSelectionListener; -import javax.swing.tree.TreePath; - -import junit.framework.Test; -import junit.framework.TestResult; - -/** - * A hierarchical view of a test run. - * The contents of a test suite is shown - * as a tree. - */ -public class TestHierarchyRunView implements TestRunView { - TestSuitePanel fTreeBrowser; - TestRunContext fTestContext; - - public TestHierarchyRunView(TestRunContext context) { - fTestContext= context; - fTreeBrowser= new TestSuitePanel(); - fTreeBrowser.getTree().addTreeSelectionListener( - new TreeSelectionListener() { - public void valueChanged(TreeSelectionEvent e) { - testSelected(); - } - } - ); - } - - public void addTab(JTabbedPane pane) { - Icon treeIcon= TestRunner.getIconResource(getClass(), "icons/hierarchy.gif"); - pane.addTab("Test Hierarchy", treeIcon, fTreeBrowser, "The test hierarchy"); - } - - public Test getSelectedTest() { - return fTreeBrowser.getSelectedTest(); - } - - public void activate() { - testSelected(); - } - - public void revealFailure(Test failure) { - JTree tree= fTreeBrowser.getTree(); - TestTreeModel model= (TestTreeModel)tree.getModel(); - Vector vpath= new Vector(); - int index= model.findTest(failure, (Test)model.getRoot(), vpath); - if (index >= 0) { - Object[] path= new Object[vpath.size()+1]; - vpath.copyInto(path); - Object last= path[vpath.size()-1]; - path[vpath.size()]= model.getChild(last, index); - TreePath selectionPath= new TreePath(path); - tree.setSelectionPath(selectionPath); - tree.makeVisible(selectionPath); - } - } - - public void aboutToStart(Test suite, TestResult result) { - fTreeBrowser.showTestTree(suite); - result.addListener(fTreeBrowser); - } - - public void runFinished(Test suite, TestResult result) { - result.removeListener(fTreeBrowser); - } - - protected void testSelected() { - fTestContext.handleTestSelected(getSelectedTest()); - } -} diff --git a/src/junit/swingui/TestRunContext.java b/src/junit/swingui/TestRunContext.java deleted file mode 100644 index 038e3c4..0000000 --- a/src/junit/swingui/TestRunContext.java +++ /dev/null @@ -1,21 +0,0 @@ -package junit.swingui; - -import javax.swing.ListModel; - -import junit.framework.Test; - -/** - * The interface for accessing the Test run context. Test run views - * should use this interface rather than accessing the TestRunner - * directly. - */ -public interface TestRunContext { - /** - * Handles the selection of a Test. - */ - public void handleTestSelected(Test test); - /** - * Returns the failure model - */ - public ListModel getFailures(); -}
\ No newline at end of file diff --git a/src/junit/swingui/TestRunView.java b/src/junit/swingui/TestRunView.java deleted file mode 100644 index 1eb5491..0000000 --- a/src/junit/swingui/TestRunView.java +++ /dev/null @@ -1,39 +0,0 @@ -package junit.swingui; - -import javax.swing.JTabbedPane; - -import junit.framework.Test; -import junit.framework.TestResult; - -/** - * A TestRunView is shown as a page in a tabbed folder. - * It contributes the page contents and can return - * the currently selected tests. A TestRunView is - * notified about the start and finish of a run. - */ -interface TestRunView { - /** - * Returns the currently selected Test in the View - */ - public Test getSelectedTest(); - /** - * Activates the TestRunView - */ - public void activate(); - /** - * Reveals the given failure - */ - public void revealFailure(Test failure); - /** - * Adds the TestRunView to the test run views tab - */ - public void addTab(JTabbedPane pane); - /** - * Informs that the suite is about to start - */ - public void aboutToStart(Test suite, TestResult result); - /** - * Informs that the run of the test suite has finished - */ - public void runFinished(Test suite, TestResult result); -}
\ No newline at end of file diff --git a/src/junit/swingui/TestRunner.java b/src/junit/swingui/TestRunner.java deleted file mode 100644 index 44aa7a7..0000000 --- a/src/junit/swingui/TestRunner.java +++ /dev/null @@ -1,849 +0,0 @@ -package junit.swingui; - -import java.awt.BorderLayout; -import java.awt.Component; -import java.awt.GridBagConstraints; -import java.awt.GridBagLayout; -import java.awt.GridLayout; -import java.awt.Image; -import java.awt.Insets; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.awt.event.ItemEvent; -import java.awt.event.ItemListener; -import java.awt.event.KeyAdapter; -import java.awt.event.KeyEvent; -import java.awt.event.WindowAdapter; -import java.awt.event.WindowEvent; -import java.io.BufferedReader; -import java.io.BufferedWriter; -import java.io.File; -import java.io.FileReader; -import java.io.FileWriter; -import java.io.IOException; -import java.lang.reflect.Constructor; -import java.net.URL; -import java.util.Enumeration; -import java.util.Vector; - -import javax.swing.DefaultListModel; -import javax.swing.Icon; -import javax.swing.ImageIcon; -import javax.swing.JButton; -import javax.swing.JCheckBox; -import javax.swing.JComboBox; -import javax.swing.JFrame; -import javax.swing.JLabel; -import javax.swing.JMenu; -import javax.swing.JMenuBar; -import javax.swing.JMenuItem; -import javax.swing.JOptionPane; -import javax.swing.JPanel; -import javax.swing.JScrollPane; -import javax.swing.JSeparator; -import javax.swing.JSplitPane; -import javax.swing.JTabbedPane; -import javax.swing.ListModel; -import javax.swing.ScrollPaneConstants; -import javax.swing.SwingConstants; -import javax.swing.SwingUtilities; -import javax.swing.event.ChangeEvent; -import javax.swing.event.ChangeListener; -import javax.swing.event.DocumentEvent; - -import junit.framework.Test; -import junit.framework.TestCase; -import junit.framework.TestFailure; -import junit.framework.TestResult; -import junit.framework.TestSuite; -import junit.runner.BaseTestRunner; -import junit.runner.FailureDetailView; -import junit.runner.SimpleTestCollector; -import junit.runner.TestCollector; -import junit.runner.TestRunListener; -import junit.runner.Version; - -/** - * A Swing based user interface to run tests. - * Enter the name of a class which either provides a static - * suite method or is a subclass of TestCase. - * <pre> - * Synopsis: java junit.swingui.TestRunner [-noloading] [TestCase] - * </pre> - * TestRunner takes as an optional argument the name of the testcase class to be run. - */ -public class TestRunner extends BaseTestRunner implements TestRunContext { - private static final int GAP= 4; - private static final int HISTORY_LENGTH= 5; - - protected JFrame fFrame; - private Thread fRunner; - private TestResult fTestResult; - - private JComboBox fSuiteCombo; - private ProgressBar fProgressIndicator; - private DefaultListModel fFailures; - private JLabel fLogo; - private CounterPanel fCounterPanel; - private JButton fRun; - private JButton fQuitButton; - private JButton fRerunButton; - private StatusLine fStatusLine; - private FailureDetailView fFailureView; - private JTabbedPane fTestViewTab; - private JCheckBox fUseLoadingRunner; - private Vector fTestRunViews= new Vector(); // view associated with tab in tabbed pane - - private static final String TESTCOLLECTOR_KEY= "TestCollectorClass"; - private static final String FAILUREDETAILVIEW_KEY= "FailureViewClass"; - - public TestRunner() { - } - - public static void main(String[] args) { - new TestRunner().start(args); - } - - public static void run(Class test) { - String args[]= { test.getName() }; - main(args); - } - - public void testFailed(final int status, final Test test, final Throwable t) { - SwingUtilities.invokeLater( - new Runnable() { - public void run() { - switch (status) { - case TestRunListener.STATUS_ERROR: - fCounterPanel.setErrorValue(fTestResult.errorCount()); - appendFailure(test, t); - break; - case TestRunListener.STATUS_FAILURE: - fCounterPanel.setFailureValue(fTestResult.failureCount()); - appendFailure(test, t); - break; - } - } - } - ); - } - - public void testStarted(String testName) { - postInfo("Running: "+testName); - } - - public void testEnded(String stringName) { - synchUI(); - SwingUtilities.invokeLater( - new Runnable() { - public void run() { - if (fTestResult != null) { - fCounterPanel.setRunValue(fTestResult.runCount()); - fProgressIndicator.step(fTestResult.runCount(), fTestResult.wasSuccessful()); - } - } - } - ); - } - - public void setSuite(String suiteName) { - fSuiteCombo.getEditor().setItem(suiteName); - } - - private void addToHistory(final String suite) { - for (int i= 0; i < fSuiteCombo.getItemCount(); i++) { - if (suite.equals(fSuiteCombo.getItemAt(i))) { - fSuiteCombo.removeItemAt(i); - fSuiteCombo.insertItemAt(suite, 0); - fSuiteCombo.setSelectedIndex(0); - return; - } - } - fSuiteCombo.insertItemAt(suite, 0); - fSuiteCombo.setSelectedIndex(0); - pruneHistory(); - } - - private void pruneHistory() { - int historyLength= getPreference("maxhistory", HISTORY_LENGTH); - if (historyLength < 1) - historyLength= 1; - for (int i= fSuiteCombo.getItemCount()-1; i > historyLength-1; i--) - fSuiteCombo.removeItemAt(i); - } - - private void appendFailure(Test test, Throwable t) { - fFailures.addElement(new TestFailure(test, t)); - if (fFailures.size() == 1) - revealFailure(test); - } - - private void revealFailure(Test test) { - for (Enumeration e= fTestRunViews.elements(); e.hasMoreElements(); ) { - TestRunView v= (TestRunView) e.nextElement(); - v.revealFailure(test); - } - } - - protected void aboutToStart(final Test testSuite) { - for (Enumeration e= fTestRunViews.elements(); e.hasMoreElements(); ) { - TestRunView v= (TestRunView) e.nextElement(); - v.aboutToStart(testSuite, fTestResult); - } - } - - protected void runFinished(final Test testSuite) { - SwingUtilities.invokeLater( - new Runnable() { - public void run() { - for (Enumeration e= fTestRunViews.elements(); e.hasMoreElements(); ) { - TestRunView v= (TestRunView) e.nextElement(); - v.runFinished(testSuite, fTestResult); - } - } - } - ); - } - - protected CounterPanel createCounterPanel() { - return new CounterPanel(); - } - - protected JPanel createFailedPanel() { - JPanel failedPanel= new JPanel(new GridLayout(0, 1, 0, 2)); - fRerunButton= new JButton("Run"); - fRerunButton.setEnabled(false); - fRerunButton.addActionListener( - new ActionListener() { - public void actionPerformed(ActionEvent e) { - rerun(); - } - } - ); - failedPanel.add(fRerunButton); - return failedPanel; - } - - protected FailureDetailView createFailureDetailView() { - String className= BaseTestRunner.getPreference(FAILUREDETAILVIEW_KEY); - if (className != null) { - Class viewClass= null; - try { - viewClass= Class.forName(className); - return (FailureDetailView)viewClass.newInstance(); - } catch(Exception e) { - JOptionPane.showMessageDialog(fFrame, "Could not create Failure DetailView - using default view"); - } - } - return new DefaultFailureDetailView(); - } - - /** - * Creates the JUnit menu. Clients override this - * method to add additional menu items. - */ - protected JMenu createJUnitMenu() { - JMenu menu= new JMenu("JUnit"); - menu.setMnemonic('J'); - JMenuItem mi1= new JMenuItem("About..."); - mi1.addActionListener( - new ActionListener() { - public void actionPerformed(ActionEvent event) { - about(); - } - } - ); - mi1.setMnemonic('A'); - menu.add(mi1); - - menu.addSeparator(); - JMenuItem mi2= new JMenuItem(" Exit "); - mi2.addActionListener( - new ActionListener() { - public void actionPerformed(ActionEvent event) { - terminate(); - } - } - ); - mi2.setMnemonic('x'); - menu.add(mi2); - - return menu; - } - - protected JFrame createFrame() { - JFrame frame= new JFrame("JUnit"); - Image icon= loadFrameIcon(); - if (icon != null) - frame.setIconImage(icon); - frame.getContentPane().setLayout(new BorderLayout(0, 0)); - - frame.addWindowListener( - new WindowAdapter() { - public void windowClosing(WindowEvent e) { - terminate(); - } - } - ); - return frame; - } - - protected JLabel createLogo() { - JLabel label; - Icon icon= getIconResource(BaseTestRunner.class, "logo.gif"); - if (icon != null) - label= new JLabel(icon); - else - label= new JLabel("JV"); - label.setToolTipText("JUnit Version "+Version.id()); - return label; - } - - protected void createMenus(JMenuBar mb) { - mb.add(createJUnitMenu()); - } - - protected JCheckBox createUseLoaderCheckBox() { - boolean useLoader= useReloadingTestSuiteLoader(); - JCheckBox box= new JCheckBox("Reload classes every run", useLoader); - box.setToolTipText("Use a custom class loader to reload the classes for every run"); - if (inVAJava()) - box.setVisible(false); - return box; - } - - protected JButton createQuitButton() { - // spaces required to avoid layout flicker - // Exit is shorter than Stop that shows in the same column - JButton quit= new JButton(" Exit "); - quit.addActionListener( - new ActionListener() { - public void actionPerformed(ActionEvent e) { - terminate(); - } - } - ); - return quit; - } - - protected JButton createRunButton() { - JButton run= new JButton("Run"); - run.setEnabled(true); - run.addActionListener( - new ActionListener() { - public void actionPerformed(ActionEvent e) { - runSuite(); - } - } - ); - return run; - } - - protected Component createBrowseButton() { - JButton browse= new JButton("..."); - browse.setToolTipText("Select a Test class"); - browse.addActionListener( - new ActionListener() { - public void actionPerformed(ActionEvent e) { - browseTestClasses(); - } - } - ); - return browse; - } - - protected StatusLine createStatusLine() { - return new StatusLine(380); - } - - protected JComboBox createSuiteCombo() { - JComboBox combo= new JComboBox(); - combo.setEditable(true); - combo.setLightWeightPopupEnabled(false); - - combo.getEditor().getEditorComponent().addKeyListener( - new KeyAdapter() { - public void keyTyped(KeyEvent e) { - textChanged(); - if (e.getKeyChar() == KeyEvent.VK_ENTER) - runSuite(); - } - } - ); - try { - loadHistory(combo); - } catch (IOException e) { - // fails the first time - } - combo.addItemListener( - new ItemListener() { - public void itemStateChanged(ItemEvent event) { - if (event.getStateChange() == ItemEvent.SELECTED) { - textChanged(); - } - } - } - ); - return combo; - } - - protected JTabbedPane createTestRunViews() { - JTabbedPane pane= new JTabbedPane(SwingConstants.BOTTOM); - - FailureRunView lv= new FailureRunView(this); - fTestRunViews.addElement(lv); - lv.addTab(pane); - - TestHierarchyRunView tv= new TestHierarchyRunView(this); - fTestRunViews.addElement(tv); - tv.addTab(pane); - - pane.addChangeListener( - new ChangeListener() { - public void stateChanged(ChangeEvent e) { - testViewChanged(); - } - } - ); - return pane; - } - - public void testViewChanged() { - TestRunView view= (TestRunView)fTestRunViews.elementAt(fTestViewTab.getSelectedIndex()); - view.activate(); - } - - protected TestResult createTestResult() { - return new TestResult(); - } - - protected JFrame createUI(String suiteName) { - JFrame frame= createFrame(); - JMenuBar mb= new JMenuBar(); - createMenus(mb); - frame.setJMenuBar(mb); - - JLabel suiteLabel= new JLabel("Test class name:"); - fSuiteCombo= createSuiteCombo(); - fRun= createRunButton(); - frame.getRootPane().setDefaultButton(fRun); - Component browseButton= createBrowseButton(); - - fUseLoadingRunner= createUseLoaderCheckBox(); - - fStatusLine= createStatusLine(); - if (inMac()) - fProgressIndicator= new MacProgressBar(fStatusLine); - else - fProgressIndicator= new ProgressBar(); - fCounterPanel= createCounterPanel(); - - fFailures= new DefaultListModel(); - - fTestViewTab= createTestRunViews(); - JPanel failedPanel= createFailedPanel(); - - fFailureView= createFailureDetailView(); - JScrollPane tracePane= new JScrollPane(fFailureView.getComponent(), ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS, ScrollPaneConstants.HORIZONTAL_SCROLLBAR_ALWAYS); - - - - fQuitButton= createQuitButton(); - fLogo= createLogo(); - - JPanel panel= new JPanel(new GridBagLayout()); - - addGrid(panel, suiteLabel, 0, 0, 2, GridBagConstraints.HORIZONTAL, 1.0, GridBagConstraints.WEST); - addGrid(panel, fSuiteCombo, 0, 1, 1, GridBagConstraints.HORIZONTAL, 1.0, GridBagConstraints.WEST); - addGrid(panel, browseButton, 1, 1, 1, GridBagConstraints.NONE, 0.0, GridBagConstraints.WEST); - addGrid(panel, fRun, 2, 1, 1, GridBagConstraints.HORIZONTAL, 0.0, GridBagConstraints.CENTER); - - addGrid(panel, fUseLoadingRunner, 0, 2, 3, GridBagConstraints.NONE, 1.0, GridBagConstraints.WEST); - //addGrid(panel, new JSeparator(), 0, 3, 3, GridBagConstraints.HORIZONTAL, 1.0, GridBagConstraints.WEST); - - - addGrid(panel, fProgressIndicator, 0, 3, 2, GridBagConstraints.HORIZONTAL, 1.0, GridBagConstraints.WEST); - addGrid(panel, fLogo, 2, 3, 1, GridBagConstraints.NONE, 0.0, GridBagConstraints.NORTH); - - addGrid(panel, fCounterPanel, 0, 4, 2, GridBagConstraints.NONE, 0.0, GridBagConstraints.WEST); - addGrid(panel, new JSeparator(), 0, 5, 2, GridBagConstraints.HORIZONTAL, 1.0, GridBagConstraints.WEST); - addGrid(panel, new JLabel("Results:"), 0, 6, 2, GridBagConstraints.HORIZONTAL, 1.0, GridBagConstraints.WEST); - - JSplitPane splitter= new JSplitPane(JSplitPane.VERTICAL_SPLIT, fTestViewTab, tracePane); - addGrid(panel, splitter, 0, 7, 2, GridBagConstraints.BOTH, 1.0, GridBagConstraints.WEST); - - addGrid(panel, failedPanel, 2, 7, 1, GridBagConstraints.HORIZONTAL, 0.0, GridBagConstraints.NORTH/*CENTER*/); - - addGrid(panel, fStatusLine, 0, 9, 2, GridBagConstraints.HORIZONTAL, 1.0, GridBagConstraints.CENTER); - addGrid(panel, fQuitButton, 2, 9, 1, GridBagConstraints.HORIZONTAL, 0.0, GridBagConstraints.CENTER); - - frame.setContentPane(panel); - frame.pack(); - frame.setLocation(200, 200); - return frame; - } - - private void addGrid(JPanel p, Component co, int x, int y, int w, int fill, double wx, int anchor) { - GridBagConstraints c= new GridBagConstraints(); - c.gridx= x; c.gridy= y; - c.gridwidth= w; - c.anchor= anchor; - c.weightx= wx; - c.fill= fill; - if (fill == GridBagConstraints.BOTH || fill == GridBagConstraints.VERTICAL) - c.weighty= 1.0; - c.insets= new Insets(y == 0 ? 10 : 0, x == 0 ? 10 : GAP, GAP, GAP); - p.add(co, c); - } - - protected String getSuiteText() { - if (fSuiteCombo == null) - return ""; - return (String)fSuiteCombo.getEditor().getItem(); - } - - public ListModel getFailures() { - return fFailures; - } - - public void insertUpdate(DocumentEvent event) { - textChanged(); - } - - protected Object instanciateClass(String fullClassName, Object param) { - try { - Class clazz= Class.forName(fullClassName); - if (param == null) { - return clazz.newInstance(); - } else { - Class[] clazzParam= {param.getClass()}; - Constructor clazzConstructor= clazz.getConstructor(clazzParam); - Object[] objectParam= {param}; - return clazzConstructor.newInstance(objectParam); - } - } catch (Exception e) { - e.printStackTrace(); - } - return null; - } - - public void browseTestClasses() { - TestCollector collector= createTestCollector(); - TestSelector selector= new TestSelector(fFrame, collector); - if (selector.isEmpty()) { - JOptionPane.showMessageDialog(fFrame, "No Test Cases found.\nCheck that the configured \'TestCollector\' is supported on this platform."); - return; - } - selector.show(); - String className= selector.getSelectedItem(); - if (className != null) - setSuite(className); - } - - TestCollector createTestCollector() { - String className= BaseTestRunner.getPreference(TESTCOLLECTOR_KEY); - if (className != null) { - Class collectorClass= null; - try { - collectorClass= Class.forName(className); - return (TestCollector)collectorClass.newInstance(); - } catch(Exception e) { - JOptionPane.showMessageDialog(fFrame, "Could not create TestCollector - using default collector"); - } - } - return new SimpleTestCollector(); - } - - private Image loadFrameIcon() { - ImageIcon icon= (ImageIcon)getIconResource(BaseTestRunner.class, "smalllogo.gif"); - if (icon != null) - return icon.getImage(); - return null; - } - - private void loadHistory(JComboBox combo) throws IOException { - BufferedReader br= new BufferedReader(new FileReader(getSettingsFile())); - int itemCount= 0; - try { - String line; - while ((line= br.readLine()) != null) { - combo.addItem(line); - itemCount++; - } - if (itemCount > 0) - combo.setSelectedIndex(0); - - } finally { - br.close(); - } - } - - private File getSettingsFile() { - String home= System.getProperty("user.home"); - return new File(home,".junitsession"); - } - - private void postInfo(final String message) { - SwingUtilities.invokeLater( - new Runnable() { - public void run() { - showInfo(message); - } - } - ); - } - - private void postStatus(final String status) { - SwingUtilities.invokeLater( - new Runnable() { - public void run() { - showStatus(status); - } - } - ); - } - - public void removeUpdate(DocumentEvent event) { - textChanged(); - } - - private void rerun() { - TestRunView view= (TestRunView)fTestRunViews.elementAt(fTestViewTab.getSelectedIndex()); - Test rerunTest= view.getSelectedTest(); - if (rerunTest != null) - rerunTest(rerunTest); - } - - private void rerunTest(Test test) { - if (!(test instanceof TestCase)) { - showInfo("Could not reload "+ test.toString()); - return; - } - Test reloadedTest= null; - TestCase rerunTest= (TestCase)test; - - try { - Class reloadedTestClass= getLoader().reload(test.getClass()); - reloadedTest= TestSuite.createTest(reloadedTestClass, rerunTest.getName()); - } catch(Exception e) { - showInfo("Could not reload "+ test.toString()); - return; - } - TestResult result= new TestResult(); - reloadedTest.run(result); - - String message= reloadedTest.toString(); - if(result.wasSuccessful()) - showInfo(message+" was successful"); - else if (result.errorCount() == 1) - showStatus(message+" had an error"); - else - showStatus(message+" had a failure"); - } - - protected void reset() { - fCounterPanel.reset(); - fProgressIndicator.reset(); - fRerunButton.setEnabled(false); - fFailureView.clear(); - fFailures.clear(); - } - - protected void runFailed(String message) { - showStatus(message); - fRun.setText("Run"); - fRunner= null; - } - - synchronized public void runSuite() { - if (fRunner != null) { - fTestResult.stop(); - } else { - setLoading(shouldReload()); - reset(); - showInfo("Load Test Case..."); - final String suiteName= getSuiteText(); - final Test testSuite= getTest(suiteName); - if (testSuite != null) { - addToHistory(suiteName); - doRunTest(testSuite); - } - } - } - - private boolean shouldReload() { - return !inVAJava() && fUseLoadingRunner.isSelected(); - } - - - synchronized protected void runTest(final Test testSuite) { - if (fRunner != null) { - fTestResult.stop(); - } else { - reset(); - if (testSuite != null) { - doRunTest(testSuite); - } - } - } - - private void doRunTest(final Test testSuite) { - setButtonLabel(fRun, "Stop"); - fRunner= new Thread("TestRunner-Thread") { - public void run() { - TestRunner.this.start(testSuite); - postInfo("Running..."); - - long startTime= System.currentTimeMillis(); - testSuite.run(fTestResult); - - if (fTestResult.shouldStop()) { - postStatus("Stopped"); - } else { - long endTime= System.currentTimeMillis(); - long runTime= endTime-startTime; - postInfo("Finished: " + elapsedTimeAsString(runTime) + " seconds"); - } - runFinished(testSuite); - setButtonLabel(fRun, "Run"); - fRunner= null; - System.gc(); - } - }; - // make sure that the test result is created before we start the - // test runner thread so that listeners can register for it. - fTestResult= createTestResult(); - fTestResult.addListener(TestRunner.this); - aboutToStart(testSuite); - - fRunner.start(); - } - - private void saveHistory() throws IOException { - BufferedWriter bw= new BufferedWriter(new FileWriter(getSettingsFile())); - try { - for (int i= 0; i < fSuiteCombo.getItemCount(); i++) { - String testsuite= fSuiteCombo.getItemAt(i).toString(); - bw.write(testsuite, 0, testsuite.length()); - bw.newLine(); - } - } finally { - bw.close(); - } - } - - private void setButtonLabel(final JButton button, final String label) { - SwingUtilities.invokeLater( - new Runnable() { - public void run() { - button.setText(label); - } - } - ); - } - - public void handleTestSelected(Test test) { - fRerunButton.setEnabled(test != null && (test instanceof TestCase)); - showFailureDetail(test); - } - - private void showFailureDetail(Test test) { - if (test != null) { - ListModel failures= getFailures(); - for (int i= 0; i < failures.getSize(); i++) { - TestFailure failure= (TestFailure)failures.getElementAt(i); - if (failure.failedTest() == test) { - fFailureView.showFailure(failure); - return; - } - } - } - fFailureView.clear(); - } - - private void showInfo(String message) { - fStatusLine.showInfo(message); - } - - private void showStatus(String status) { - fStatusLine.showError(status); - } - - /** - * Starts the TestRunner - */ - public void start(String[] args) { - String suiteName= processArguments(args); - fFrame= createUI(suiteName); - fFrame.pack(); - fFrame.setVisible(true); - - if (suiteName != null) { - setSuite(suiteName); - runSuite(); - } - } - - private void start(final Test test) { - SwingUtilities.invokeLater( - new Runnable() { - public void run() { - int total= test.countTestCases(); - fProgressIndicator.start(total); - fCounterPanel.setTotal(total); - } - } - ); - } - - /** - * Wait until all the events are processed in the event thread - */ - private void synchUI() { - try { - SwingUtilities.invokeAndWait( - new Runnable() { - public void run() {} - } - ); - } - catch (Exception e) { - } - } - - /** - * Terminates the TestRunner - */ - public void terminate() { - fFrame.dispose(); - try { - saveHistory(); - } catch (IOException e) { - System.out.println("Couldn't save test run history"); - } - System.exit(0); - } - - public void textChanged() { - fRun.setEnabled(getSuiteText().length() > 0); - clearStatus(); - } - - protected void clearStatus() { - fStatusLine.clear(); - } - - public static Icon getIconResource(Class clazz, String name) { - URL url= clazz.getResource(name); - if (url == null) { - System.err.println("Warning: could not load \""+name+"\" icon"); - return null; - } - return new ImageIcon(url); - } - - private void about() { - AboutDialog about= new AboutDialog(fFrame); - about.show(); - } -} diff --git a/src/junit/swingui/TestSelector.java b/src/junit/swingui/TestSelector.java deleted file mode 100644 index f0f1f9e..0000000 --- a/src/junit/swingui/TestSelector.java +++ /dev/null @@ -1,285 +0,0 @@ -package junit.swingui; - -import java.awt.Component; -import java.awt.Cursor; -import java.awt.Dimension; -import java.awt.Frame; -import java.awt.GridBagConstraints; -import java.awt.GridBagLayout; -import java.awt.Insets; -import java.awt.Toolkit; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.awt.event.KeyAdapter; -import java.awt.event.KeyEvent; -import java.awt.event.MouseAdapter; -import java.awt.event.MouseEvent; -import java.awt.event.WindowAdapter; -import java.awt.event.WindowEvent; -import java.util.Enumeration; -import java.util.Vector; - -import javax.swing.DefaultListCellRenderer; -import javax.swing.Icon; -import javax.swing.JButton; -import javax.swing.JDialog; -import javax.swing.JLabel; -import javax.swing.JList; -import javax.swing.JScrollPane; -import javax.swing.ListModel; -import javax.swing.ListSelectionModel; -import javax.swing.UIManager; -import javax.swing.event.ListSelectionEvent; -import javax.swing.event.ListSelectionListener; - -import junit.runner.Sorter; -import junit.runner.TestCollector; - -/** - * A test class selector. A simple dialog to pick the name of a test suite. - */ -public class TestSelector extends JDialog { - private JButton fCancel; - private JButton fOk; - private JList fList; - private JScrollPane fScrolledList; - private JLabel fDescription; - private String fSelectedItem; - - /** - * Renders TestFailures in a JList - */ - static class TestCellRenderer extends DefaultListCellRenderer { - Icon fLeafIcon; - Icon fSuiteIcon; - - public TestCellRenderer() { - fLeafIcon= UIManager.getIcon("Tree.leafIcon"); - fSuiteIcon= UIManager.getIcon("Tree.closedIcon"); - } - - public Component getListCellRendererComponent( - JList list, Object value, int modelIndex, - boolean isSelected, boolean cellHasFocus) { - Component c= super.getListCellRendererComponent(list, value, modelIndex, isSelected, cellHasFocus); - String displayString= displayString((String)value); - - if (displayString.startsWith("AllTests")) - setIcon(fSuiteIcon); - else - setIcon(fLeafIcon); - - setText(displayString); - return c; - } - - public static String displayString(String className) { - int typeIndex= className.lastIndexOf('.'); - if (typeIndex < 0) - return className; - return className.substring(typeIndex+1) + " - " + className.substring(0, typeIndex); - } - - public static boolean matchesKey(String s, char ch) { - return ch == Character.toUpperCase(s.charAt(typeIndex(s))); - } - - private static int typeIndex(String s) { - int typeIndex= s.lastIndexOf('.'); - int i= 0; - if (typeIndex > 0) - i= typeIndex+1; - return i; - } - } - - protected class DoubleClickListener extends MouseAdapter { - public void mouseClicked(MouseEvent e) { - if (e.getClickCount() == 2) { - okSelected(); - } - } - } - - protected class KeySelectListener extends KeyAdapter { - public void keyTyped(KeyEvent e) { - keySelectTestClass(e.getKeyChar()); - } - } - - public TestSelector(Frame parent, TestCollector testCollector) { - super(parent, true); - setSize(350, 300); - setResizable(false); - // setLocationRelativeTo only exists in 1.4 - try { - setLocationRelativeTo(parent); - } catch (NoSuchMethodError e) { - centerWindow(this); - } - setTitle("Test Selector"); - - Vector list= null; - try { - parent.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); - list= createTestList(testCollector); - } finally { - parent.setCursor(Cursor.getDefaultCursor()); - } - fList= new JList(list); - fList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); - fList.setCellRenderer(new TestCellRenderer()); - fScrolledList= new JScrollPane(fList); - - fCancel= new JButton("Cancel"); - fDescription= new JLabel("Select the Test class:"); - fOk= new JButton("OK"); - fOk.setEnabled(false); - getRootPane().setDefaultButton(fOk); - - defineLayout(); - addListeners(); - } - - public static void centerWindow(Component c) { - Dimension paneSize= c.getSize(); - Dimension screenSize= c.getToolkit().getScreenSize(); - c.setLocation((screenSize.width-paneSize.width)/2, (screenSize.height-paneSize.height)/2); - } - - private void addListeners() { - fCancel.addActionListener( - new ActionListener() { - public void actionPerformed(ActionEvent e) { - dispose(); - } - } - ); - - fOk.addActionListener( - new ActionListener() { - public void actionPerformed(ActionEvent e) { - okSelected(); - } - } - ); - - fList.addMouseListener(new DoubleClickListener()); - fList.addKeyListener(new KeySelectListener()); - fList.addListSelectionListener( - new ListSelectionListener() { - public void valueChanged(ListSelectionEvent e) { - checkEnableOK(e); - } - } - ); - - addWindowListener( - new WindowAdapter() { - public void windowClosing(WindowEvent e) { - dispose(); - } - } - ); - } - - private void defineLayout() { - getContentPane().setLayout(new GridBagLayout()); - GridBagConstraints labelConstraints = new GridBagConstraints(); - labelConstraints.gridx= 0; labelConstraints.gridy= 0; - labelConstraints.gridwidth= 1; labelConstraints.gridheight= 1; - labelConstraints.fill= GridBagConstraints.BOTH; - labelConstraints.anchor= GridBagConstraints.WEST; - labelConstraints.weightx= 1.0; - labelConstraints.weighty= 0.0; - labelConstraints.insets= new Insets(8, 8, 0, 8); - getContentPane().add(fDescription, labelConstraints); - - GridBagConstraints listConstraints = new GridBagConstraints(); - listConstraints.gridx= 0; listConstraints.gridy= 1; - listConstraints.gridwidth= 4; listConstraints.gridheight= 1; - listConstraints.fill= GridBagConstraints.BOTH; - listConstraints.anchor= GridBagConstraints.CENTER; - listConstraints.weightx= 1.0; - listConstraints.weighty= 1.0; - listConstraints.insets= new Insets(8, 8, 8, 8); - getContentPane().add(fScrolledList, listConstraints); - - GridBagConstraints okConstraints= new GridBagConstraints(); - okConstraints.gridx= 2; okConstraints.gridy= 2; - okConstraints.gridwidth= 1; okConstraints.gridheight= 1; - okConstraints.anchor= java.awt.GridBagConstraints.EAST; - okConstraints.insets= new Insets(0, 8, 8, 8); - getContentPane().add(fOk, okConstraints); - - - GridBagConstraints cancelConstraints = new GridBagConstraints(); - cancelConstraints.gridx= 3; cancelConstraints.gridy= 2; - cancelConstraints.gridwidth= 1; cancelConstraints.gridheight= 1; - cancelConstraints.anchor= java.awt.GridBagConstraints.EAST; - cancelConstraints.insets= new Insets(0, 8, 8, 8); - getContentPane().add(fCancel, cancelConstraints); - } - - public void checkEnableOK(ListSelectionEvent e) { - fOk.setEnabled(fList.getSelectedIndex() != -1); - } - - public void okSelected() { - fSelectedItem= (String)fList.getSelectedValue(); - dispose(); - } - - public boolean isEmpty() { - return fList.getModel().getSize() == 0; - } - - public void keySelectTestClass(char ch) { - ListModel model= fList.getModel(); - if (!Character.isJavaIdentifierStart(ch)) - return; - for (int i= 0; i < model.getSize(); i++) { - String s= (String)model.getElementAt(i); - if (TestCellRenderer.matchesKey(s, Character.toUpperCase(ch))) { - fList.setSelectedIndex(i); - fList.ensureIndexIsVisible(i); - return; - } - } - Toolkit.getDefaultToolkit().beep(); - } - - public String getSelectedItem() { - return fSelectedItem; - } - - private Vector createTestList(TestCollector collector) { - Enumeration each= collector.collectTests(); - Vector v= new Vector(200); - Vector displayVector= new Vector(v.size()); - while(each.hasMoreElements()) { - String s= (String)each.nextElement(); - v.addElement(s); - displayVector.addElement(TestCellRenderer.displayString(s)); - } - if (v.size() > 0) - Sorter.sortStrings(displayVector, 0, displayVector.size()-1, new ParallelSwapper(v)); - return v; - } - - private class ParallelSwapper implements Sorter.Swapper { - Vector fOther; - - ParallelSwapper(Vector other) { - fOther= other; - } - public void swap(Vector values, int left, int right) { - Object tmp= values.elementAt(left); - values.setElementAt(values.elementAt(right), left); - values.setElementAt(tmp, right); - Object tmp2= fOther.elementAt(left); - fOther.setElementAt(fOther.elementAt(right), left); - fOther.setElementAt(tmp2, right); - } - } -}
\ No newline at end of file diff --git a/src/junit/swingui/TestSuitePanel.java b/src/junit/swingui/TestSuitePanel.java deleted file mode 100644 index d8902ad..0000000 --- a/src/junit/swingui/TestSuitePanel.java +++ /dev/null @@ -1,172 +0,0 @@ -package junit.swingui; - -import java.awt.BorderLayout; -import java.awt.Component; -import java.awt.Dimension; -import java.util.Vector; - -import javax.swing.Icon; -import javax.swing.JComponent; -import javax.swing.JPanel; -import javax.swing.JScrollPane; -import javax.swing.JTree; -import javax.swing.SwingUtilities; -import javax.swing.ToolTipManager; -import javax.swing.tree.DefaultTreeCellRenderer; -import javax.swing.tree.TreeModel; -import javax.swing.tree.TreePath; - -import junit.framework.AssertionFailedError; -import junit.framework.Test; -import junit.framework.TestListener; - -/** - * A Panel showing a test suite as a tree. - */ -class TestSuitePanel extends JPanel implements TestListener { - private JTree fTree; - private JScrollPane fScrollTree; - private TestTreeModel fModel; - - static class TestTreeCellRenderer extends DefaultTreeCellRenderer { - private Icon fErrorIcon; - private Icon fOkIcon; - private Icon fFailureIcon; - - TestTreeCellRenderer() { - super(); - loadIcons(); - } - - void loadIcons() { - fErrorIcon= TestRunner.getIconResource(getClass(), "icons/error.gif"); - fOkIcon= TestRunner.getIconResource(getClass(), "icons/ok.gif"); - fFailureIcon= TestRunner.getIconResource(getClass(), "icons/failure.gif"); - } - - String stripParenthesis(Object o) { - String text= o.toString (); - int pos= text.indexOf('('); - if (pos < 1) - return text; - return text.substring (0, pos); - } - - public Component getTreeCellRendererComponent(JTree tree, Object value, - boolean sel, boolean expanded, boolean leaf, int row, boolean hasFocus) { - - Component c= super.getTreeCellRendererComponent(tree, value, sel, expanded, leaf, row, hasFocus); - TreeModel model= tree.getModel(); - if (model instanceof TestTreeModel) { - TestTreeModel testModel= (TestTreeModel)model; - Test t= (Test)value; - String s= ""; - if (testModel.isFailure(t)) { - if (fFailureIcon != null) - setIcon(fFailureIcon); - s= " - Failed"; - } - else if (testModel.isError(t)) { - if (fErrorIcon != null) - setIcon(fErrorIcon); - s= " - Error"; - } - else if (testModel.wasRun(t)) { - if (fOkIcon != null) - setIcon(fOkIcon); - s= " - Passed"; - } - if (c instanceof JComponent) - ((JComponent)c).setToolTipText(getText()+s); - } - setText(stripParenthesis(value)); - return c; - } - } - - public TestSuitePanel() { - super(new BorderLayout()); - setPreferredSize(new Dimension(300, 100)); - fTree= new JTree(); - fTree.setModel(null); - fTree.setRowHeight(20); - ToolTipManager.sharedInstance().registerComponent(fTree); - fTree.putClientProperty("JTree.lineStyle", "Angled"); - fScrollTree= new JScrollPane(fTree); - add(fScrollTree, BorderLayout.CENTER); - } - - public void addError(final Test test, final Throwable t) { - fModel.addError(test); - fireTestChanged(test, true); - } - - public void addFailure(final Test test, final AssertionFailedError t) { - fModel.addFailure(test); - fireTestChanged(test, true); - } - - /** - * A test ended. - */ - public void endTest(Test test) { - fModel.addRunTest(test); - fireTestChanged(test, false); - } - - /** - * A test started. - */ - public void startTest(Test test) { - } - - /** - * Returns the selected test or null if multiple or none is selected - */ - public Test getSelectedTest() { - TreePath[] paths= fTree.getSelectionPaths(); - if (paths != null && paths.length == 1) - return (Test)paths[0].getLastPathComponent(); - return null; - } - - /** - * Returns the Tree - */ - public JTree getTree() { - return fTree; - } - - /** - * Shows the test hierarchy starting at the given test - */ - public void showTestTree(Test root) { - fModel= new TestTreeModel(root); - fTree.setModel(fModel); - fTree.setCellRenderer(new TestTreeCellRenderer()); - } - - private void fireTestChanged(final Test test, final boolean expand) { - SwingUtilities.invokeLater( - new Runnable() { - public void run() { - Vector vpath= new Vector(); - int index= fModel.findTest(test, (Test)fModel.getRoot(), vpath); - if (index >= 0) { - Object[] path= new Object[vpath.size()]; - vpath.copyInto(path); - TreePath treePath= new TreePath(path); - fModel.fireNodeChanged(treePath, index); - if (expand) { - Object[] fullPath= new Object[vpath.size()+1]; - vpath.copyInto(fullPath); - fullPath[vpath.size()]= fModel.getChild(treePath.getLastPathComponent(), index);; - TreePath fullTreePath= new TreePath(fullPath); - fTree.scrollPathToVisible(fullTreePath); - } - } - } - } - ); - } -}
\ No newline at end of file diff --git a/src/junit/swingui/TestTreeModel.java b/src/junit/swingui/TestTreeModel.java deleted file mode 100644 index 9f3b0d3..0000000 --- a/src/junit/swingui/TestTreeModel.java +++ /dev/null @@ -1,190 +0,0 @@ -package junit.swingui; - -import java.util.Enumeration; -import java.util.Hashtable; -import java.util.Vector; - -import javax.swing.event.TreeModelEvent; -import javax.swing.event.TreeModelListener; -import javax.swing.tree.TreeModel; -import javax.swing.tree.TreePath; - -import junit.extensions.TestDecorator; -import junit.framework.Test; -import junit.framework.TestSuite; - -/** - * A tree model for a Test. - */ -class TestTreeModel implements TreeModel { - private Test fRoot; - private Vector fModelListeners= new Vector(); - private Hashtable fFailures= new Hashtable(); - private Hashtable fErrors= new Hashtable(); - private Hashtable fRunTests= new Hashtable(); - - /** - * Constructs a tree model with the given test as its root. - */ - public TestTreeModel(Test root) { - super(); - fRoot= root; - } - - /** - * adds a TreeModelListener - */ - public void addTreeModelListener(TreeModelListener l) { - if (!fModelListeners.contains(l)) - fModelListeners.addElement(l); - } - /** - * Removes a TestModelListener - */ - public void removeTreeModelListener(TreeModelListener l) { - fModelListeners.removeElement(l); - } - /** - * Finds the path to a test. Returns the index of the test in its - * parent test suite. - */ - public int findTest(Test target, Test node, Vector path) { - if (target.equals(node)) - return 0; - - TestSuite suite= isTestSuite(node); - for (int i= 0; i < getChildCount(node); i++) { - Test t= suite.testAt(i); - int index= findTest(target, t, path); - if (index >= 0) { - path.insertElementAt(node, 0); - if (path.size() == 1) - return i; - return index; - } - } - return -1; - } - /** - * Fires a node changed event - */ - public void fireNodeChanged(TreePath path, int index) { - int[] indices= {index}; - Object[] changedChildren= {getChild(path.getLastPathComponent(), index)}; - TreeModelEvent event= new TreeModelEvent(this, path, indices, changedChildren); - - Enumeration e= fModelListeners.elements(); - while (e.hasMoreElements()) { - TreeModelListener l= (TreeModelListener) e.nextElement(); - l.treeNodesChanged(event); - } - } - /** - * Gets the test at the given index - */ - public Object getChild(Object parent, int index) { - TestSuite suite= isTestSuite(parent); - if (suite != null) - return suite.testAt(index); - return null; - } - /** - * Gets the number of tests. - */ - public int getChildCount(Object parent) { - TestSuite suite= isTestSuite(parent); - if (suite != null) - return suite.testCount(); - return 0; - } - /** - * Gets the index of a test in a test suite - */ - public int getIndexOfChild(Object parent, Object child) { - TestSuite suite= isTestSuite(parent); - if (suite != null) { - int i= 0; - for (Enumeration e= suite.tests(); e.hasMoreElements(); i++) { - if (child.equals(e.nextElement())) - return i; - } - } - return -1; - } - /** - * Returns the root of the tree - */ - public Object getRoot() { - return fRoot; - } - /** - * Tests if the test is a leaf. - */ - public boolean isLeaf(Object node) { - return isTestSuite(node) == null; - } - /** - * Tests if the node is a TestSuite. - */ - TestSuite isTestSuite(Object node) { - if (node instanceof TestSuite) - return (TestSuite)node; - if (node instanceof TestDecorator) { - Test baseTest= ((TestDecorator)node).getTest(); - return isTestSuite(baseTest); - } - return null; - } - - /** - * Called when the value of the model object was changed in the view - */ - public void valueForPathChanged(TreePath path, Object newValue) { - // we don't support direct editing of the model - System.out.println("TreeModel.valueForPathChanged: not implemented"); - } - /** - * Remembers a test failure - */ - void addFailure(Test t) { - fFailures.put(t, t); - } - /** - * Remembers a test error - */ - void addError(Test t) { - fErrors.put(t, t); - } - /** - * Remembers that a test was run - */ - void addRunTest(Test t) { - fRunTests.put(t, t); - } - /** - * Returns whether a test was run - */ - boolean wasRun(Test t) { - return fRunTests.get(t) != null; - } - /** - * Tests whether a test was an error - */ - boolean isError(Test t) { - return (fErrors != null) && fErrors.get(t) != null; - } - /** - * Tests whether a test was a failure - */ - boolean isFailure(Test t) { - return (fFailures != null) && fFailures.get(t) != null; - } - /** - * Resets the test results - */ - void resetResults() { - fFailures= new Hashtable(); - fRunTests= new Hashtable(); - fErrors= new Hashtable(); - } -}
\ No newline at end of file diff --git a/src/junit/swingui/icons/error.gif b/src/junit/swingui/icons/error.gif Binary files differdeleted file mode 100644 index fe13a6a..0000000 --- a/src/junit/swingui/icons/error.gif +++ /dev/null diff --git a/src/junit/swingui/icons/failure.gif b/src/junit/swingui/icons/failure.gif Binary files differdeleted file mode 100644 index 156ecd6..0000000 --- a/src/junit/swingui/icons/failure.gif +++ /dev/null diff --git a/src/junit/swingui/icons/hierarchy.gif b/src/junit/swingui/icons/hierarchy.gif Binary files differdeleted file mode 100644 index 9f05ed2..0000000 --- a/src/junit/swingui/icons/hierarchy.gif +++ /dev/null diff --git a/src/junit/swingui/icons/ok.gif b/src/junit/swingui/icons/ok.gif Binary files differdeleted file mode 100644 index 034825b..0000000 --- a/src/junit/swingui/icons/ok.gif +++ /dev/null diff --git a/src/junit/textui/ResultPrinter.java b/src/junit/textui/ResultPrinter.java index 1ebb7a1..f2f01f5 100644 --- a/src/junit/textui/ResultPrinter.java +++ b/src/junit/textui/ResultPrinter.java @@ -51,14 +51,14 @@ public class ResultPrinter implements TestListener { printDefects(result.failures(), result.failureCount(), "failure"); } - protected void printDefects(Enumeration booBoos, int count, String type) { + protected void printDefects(Enumeration<TestFailure> booBoos, int count, String type) { if (count == 0) return; if (count == 1) getWriter().println("There was " + count + " " + type + ":"); else getWriter().println("There were " + count + " " + type + "s:"); for (int i= 1; booBoos.hasMoreElements(); i++) { - printDefect((TestFailure) booBoos.nextElement(), i); + printDefect(booBoos.nextElement(), i); } } diff --git a/src/junit/textui/TestRunner.java b/src/junit/textui/TestRunner.java index 01b9d6d..046448e 100644 --- a/src/junit/textui/TestRunner.java +++ b/src/junit/textui/TestRunner.java @@ -4,11 +4,10 @@ package junit.textui; import java.io.PrintStream; import junit.framework.Test; +import junit.framework.TestCase; import junit.framework.TestResult; import junit.framework.TestSuite; import junit.runner.BaseTestRunner; -import junit.runner.StandardTestSuiteLoader; -import junit.runner.TestSuiteLoader; import junit.runner.Version; /** @@ -16,16 +15,17 @@ import junit.runner.Version; * <pre> * java junit.textui.TestRunner [-wait] TestCaseClass * </pre> - * TestRunner expects the name of a TestCase class as argument. + * + * <p>TestRunner expects the name of a TestCase class as argument. * If this class defines a static <code>suite</code> method it * will be invoked and the returned test is run. Otherwise all - * the methods starting with "test" having no arguments are run. - * <p> - * When the wait command line argument is given TestRunner - * waits until the users types RETURN. - * <p> - * TestRunner prints a trace as the tests are executed followed by a - * summary at the end. + * the methods starting with "test" having no arguments are run.</p> + * + * <p> When the wait command line argument is given TestRunner + * waits until the users types RETURN.</p> + * + * <p>TestRunner prints a trace as the tests are executed followed by a + * summary at the end.</p> */ public class TestRunner extends BaseTestRunner { private ResultPrinter fPrinter; @@ -58,7 +58,7 @@ public class TestRunner extends BaseTestRunner { /** * Runs a suite extracted from a TestCase subclass. */ - static public void run(Class testClass) { + static public void run(Class<? extends TestCase> testClass) { run(new TestSuite(testClass)); } @@ -68,7 +68,7 @@ public class TestRunner extends BaseTestRunner { * from your program. * <pre> * public static void main (String[] args) { - * test.textui.TestRunner.run(suite()); + * test.textui.TestRunner.run(suite()); * } * </pre> */ @@ -86,20 +86,15 @@ public class TestRunner extends BaseTestRunner { aTestRunner.doRun(suite, true); } - /** - * Always use the StandardTestSuiteLoader. Overridden from - * BaseTestRunner. - */ - public TestSuiteLoader getLoader() { - return new StandardTestSuiteLoader(); - } - + @Override public void testFailed(int status, Test test, Throwable t) { } + @Override public void testStarted(String testName) { } + @Override public void testEnded(String testName) { } @@ -189,11 +184,12 @@ public class TestRunner extends BaseTestRunner { } protected TestResult runSingleMethod(String testCase, String method, boolean wait) throws Exception { - Class testClass= loadSuiteClass(testCase); + Class<? extends TestCase> testClass= loadSuiteClass(testCase).asSubclass(TestCase.class); Test test= TestSuite.createTest(testClass, method); return doRun(test, wait); } + @Override protected void runFailed(String message) { System.err.println(message); System.exit(FAILURE_EXIT); diff --git a/src/junit/textui/package-info.java b/src/junit/textui/package-info.java new file mode 100644 index 0000000..2aa5176 --- /dev/null +++ b/src/junit/textui/package-info.java @@ -0,0 +1,4 @@ +/** + * Provides JUnit v3.x command line based tool to run tests. + */ +package junit.textui;
\ No newline at end of file diff --git a/src/org/junit/After.java b/src/org/junit/After.java new file mode 100644 index 0000000..39aa6e5 --- /dev/null +++ b/src/org/junit/After.java @@ -0,0 +1,40 @@ +package org.junit; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * <p>If you allocate external resources in a {@link org.junit.Before} method you need to release them + * after the test runs. Annotating a <code>public void</code> method + * with <code>@After</code> causes that method to be run after the {@link org.junit.Test} method. All <code>@After</code> + * methods are guaranteed to run even if a {@link org.junit.Before} or {@link org.junit.Test} method throws an + * exception. The <code>@After</code> methods declared in superclasses will be run after those of the current + * class.</p> + * + * Here is a simple example: +* <pre> + * public class Example { + * File output; + * @Before public void createOutputFile() { + * output= new File(...); + * } + * @Test public void something() { + * ... + * } + * @After public void deleteOutputFile() { + * output.delete(); + * } + * } + * </pre> + * + * @see org.junit.Before + * @see org.junit.Test + */ + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface After { +} + diff --git a/src/org/junit/AfterClass.java b/src/org/junit/AfterClass.java new file mode 100644 index 0000000..2d6bc80 --- /dev/null +++ b/src/org/junit/AfterClass.java @@ -0,0 +1,41 @@ +package org.junit; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * <p>If you allocate expensive external resources in a {@link org.junit.BeforeClass} method you need to release them + * after all the tests in the class have run. Annotating a <code>public static void</code> method + * with <code>@AfterClass</code> causes that method to be run after all the tests in the class have been run. All <code>@AfterClass</code> + * methods are guaranteed to run even if a {@link org.junit.BeforeClass} method throws an + * exception. The <code>@AfterClass</code> methods declared in superclasses will be run after those of the current + * class.</p> + * + * Here is a simple example: +* <pre> + * public class Example { + * private static DatabaseConnection database; + * @BeforeClass public static void login() { + * database= ...; + * } + * @Test public void something() { + * ... + * } + * @Test public void somethingElse() { + * ... + * } + * @AfterClass public static void logout() { + * database.logout(); + * } + * } + * </pre> + * + * @see org.junit.BeforeClass + * @see org.junit.Test + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface AfterClass { +} diff --git a/src/org/junit/Assert.java b/src/org/junit/Assert.java new file mode 100644 index 0000000..b585b87 --- /dev/null +++ b/src/org/junit/Assert.java @@ -0,0 +1,783 @@ +package org.junit; + +import org.hamcrest.Description; +import org.hamcrest.Matcher; +import org.hamcrest.StringDescription; +import org.junit.internal.ArrayComparisonFailure; +import org.junit.internal.ExactComparisonCriteria; +import org.junit.internal.InexactComparisonCriteria; + +/** + * A set of assertion methods useful for writing tests. Only failed assertions + * are recorded. These methods can be used directly: + * <code>Assert.assertEquals(...)</code>, however, they read better if they + * are referenced through static import:<br/> + * + * <pre> + * import static org.junit.Assert.*; + * ... + * assertEquals(...); + * </pre> + * + * @see AssertionError + */ +public class Assert { + /** + * Protect constructor since it is a static only class + */ + protected Assert() { + } + + /** + * Asserts that a condition is true. If it isn't it throws an + * {@link AssertionError} with the given message. + * + * @param message + * the identifying message for the {@link AssertionError} (<code>null</code> + * okay) + * @param condition + * condition to be checked + */ + static public void assertTrue(String message, boolean condition) { + if (!condition) + fail(message); + } + + /** + * Asserts that a condition is true. If it isn't it throws an + * {@link AssertionError} without a message. + * + * @param condition + * condition to be checked + */ + static public void assertTrue(boolean condition) { + assertTrue(null, condition); + } + + /** + * Asserts that a condition is false. If it isn't it throws an + * {@link AssertionError} with the given message. + * + * @param message + * the identifying message for the {@link AssertionError} (<code>null</code> + * okay) + * @param condition + * condition to be checked + */ + static public void assertFalse(String message, boolean condition) { + assertTrue(message, !condition); + } + + /** + * Asserts that a condition is false. If it isn't it throws an + * {@link AssertionError} without a message. + * + * @param condition + * condition to be checked + */ + static public void assertFalse(boolean condition) { + assertFalse(null, condition); + } + + /** + * Fails a test with the given message. + * + * @param message + * the identifying message for the {@link AssertionError} (<code>null</code> + * okay) + * @see AssertionError + */ + static public void fail(String message) { + if (message == null) + throw new AssertionError(); + throw new AssertionError(message); + } + + /** + * Fails a test with no message. + */ + static public void fail() { + fail(null); + } + + /** + * Asserts that two objects are equal. If they are not, an + * {@link AssertionError} is thrown with the given message. If + * <code>expected</code> and <code>actual</code> are <code>null</code>, + * they are considered equal. + * + * @param message + * the identifying message for the {@link AssertionError} (<code>null</code> + * okay) + * @param expected + * expected value + * @param actual + * actual value + */ + static public void assertEquals(String message, Object expected, + Object actual) { + if (expected == null && actual == null) + return; + if (expected != null && isEquals(expected, actual)) + return; + else if (expected instanceof String && actual instanceof String) { + String cleanMessage= message == null ? "" : message; + throw new ComparisonFailure(cleanMessage, (String) expected, + (String) actual); + } else + failNotEquals(message, expected, actual); + } + + private static boolean isEquals(Object expected, Object actual) { + return expected.equals(actual); + } + + /** + * Asserts that two objects are equal. If they are not, an + * {@link AssertionError} without a message is thrown. If + * <code>expected</code> and <code>actual</code> are <code>null</code>, + * they are considered equal. + * + * @param expected + * expected value + * @param actual + * the value to check against <code>expected</code> + */ + static public void assertEquals(Object expected, Object actual) { + assertEquals(null, expected, actual); + } + + /** + * Asserts that two object arrays are equal. If they are not, an + * {@link AssertionError} is thrown with the given message. If + * <code>expecteds</code> and <code>actuals</code> are <code>null</code>, + * they are considered equal. + * + * @param message + * the identifying message for the {@link AssertionError} (<code>null</code> + * okay) + * @param expecteds + * Object array or array of arrays (multi-dimensional array) with + * expected values. + * @param actuals + * Object array or array of arrays (multi-dimensional array) with + * actual values + */ + public static void assertArrayEquals(String message, Object[] expecteds, + Object[] actuals) throws ArrayComparisonFailure { + internalArrayEquals(message, expecteds, actuals); + } + + /** + * Asserts that two object arrays are equal. If they are not, an + * {@link AssertionError} is thrown. If <code>expected</code> and + * <code>actual</code> are <code>null</code>, they are considered + * equal. + * + * @param expecteds + * Object array or array of arrays (multi-dimensional array) with + * expected values + * @param actuals + * Object array or array of arrays (multi-dimensional array) with + * actual values + */ + public static void assertArrayEquals(Object[] expecteds, Object[] actuals) { + assertArrayEquals(null, expecteds, actuals); + } + + /** + * Asserts that two byte arrays are equal. If they are not, an + * {@link AssertionError} is thrown with the given message. + * + * @param message + * the identifying message for the {@link AssertionError} (<code>null</code> + * okay) + * @param expecteds + * byte array with expected values. + * @param actuals + * byte array with actual values + */ + public static void assertArrayEquals(String message, byte[] expecteds, + byte[] actuals) throws ArrayComparisonFailure { + internalArrayEquals(message, expecteds, actuals); + } + + /** + * Asserts that two byte arrays are equal. If they are not, an + * {@link AssertionError} is thrown. + * + * @param expecteds + * byte array with expected values. + * @param actuals + * byte array with actual values + */ + public static void assertArrayEquals(byte[] expecteds, byte[] actuals) { + assertArrayEquals(null, expecteds, actuals); + } + + /** + * Asserts that two char arrays are equal. If they are not, an + * {@link AssertionError} is thrown with the given message. + * + * @param message + * the identifying message for the {@link AssertionError} (<code>null</code> + * okay) + * @param expecteds + * char array with expected values. + * @param actuals + * char array with actual values + */ + public static void assertArrayEquals(String message, char[] expecteds, + char[] actuals) throws ArrayComparisonFailure { + internalArrayEquals(message, expecteds, actuals); + } + + /** + * Asserts that two char arrays are equal. If they are not, an + * {@link AssertionError} is thrown. + * + * @param expecteds + * char array with expected values. + * @param actuals + * char array with actual values + */ + public static void assertArrayEquals(char[] expecteds, char[] actuals) { + assertArrayEquals(null, expecteds, actuals); + } + + /** + * Asserts that two short arrays are equal. If they are not, an + * {@link AssertionError} is thrown with the given message. + * + * @param message + * the identifying message for the {@link AssertionError} (<code>null</code> + * okay) + * @param expecteds + * short array with expected values. + * @param actuals + * short array with actual values + */ + public static void assertArrayEquals(String message, short[] expecteds, + short[] actuals) throws ArrayComparisonFailure { + internalArrayEquals(message, expecteds, actuals); + } + + /** + * Asserts that two short arrays are equal. If they are not, an + * {@link AssertionError} is thrown. + * + * @param expecteds + * short array with expected values. + * @param actuals + * short array with actual values + */ + public static void assertArrayEquals(short[] expecteds, short[] actuals) { + assertArrayEquals(null, expecteds, actuals); + } + + /** + * Asserts that two int arrays are equal. If they are not, an + * {@link AssertionError} is thrown with the given message. + * + * @param message + * the identifying message for the {@link AssertionError} (<code>null</code> + * okay) + * @param expecteds + * int array with expected values. + * @param actuals + * int array with actual values + */ + public static void assertArrayEquals(String message, int[] expecteds, + int[] actuals) throws ArrayComparisonFailure { + internalArrayEquals(message, expecteds, actuals); + } + + /** + * Asserts that two int arrays are equal. If they are not, an + * {@link AssertionError} is thrown. + * + * @param expecteds + * int array with expected values. + * @param actuals + * int array with actual values + */ + public static void assertArrayEquals(int[] expecteds, int[] actuals) { + assertArrayEquals(null, expecteds, actuals); + } + + /** + * Asserts that two long arrays are equal. If they are not, an + * {@link AssertionError} is thrown with the given message. + * + * @param message + * the identifying message for the {@link AssertionError} (<code>null</code> + * okay) + * @param expecteds + * long array with expected values. + * @param actuals + * long array with actual values + */ + public static void assertArrayEquals(String message, long[] expecteds, + long[] actuals) throws ArrayComparisonFailure { + internalArrayEquals(message, expecteds, actuals); + } + + /** + * Asserts that two long arrays are equal. If they are not, an + * {@link AssertionError} is thrown. + * + * @param expecteds + * long array with expected values. + * @param actuals + * long array with actual values + */ + public static void assertArrayEquals(long[] expecteds, long[] actuals) { + assertArrayEquals(null, expecteds, actuals); + } + + /** + * Asserts that two double arrays are equal. If they are not, an + * {@link AssertionError} is thrown with the given message. + * + * @param message + * the identifying message for the {@link AssertionError} (<code>null</code> + * okay) + * @param expecteds + * double array with expected values. + * @param actuals + * double array with actual values + */ + public static void assertArrayEquals(String message, double[] expecteds, + double[] actuals, double delta) throws ArrayComparisonFailure { + new InexactComparisonCriteria(delta).arrayEquals(message, expecteds, actuals); + } + + /** + * Asserts that two double arrays are equal. If they are not, an + * {@link AssertionError} is thrown. + * + * @param expecteds + * double array with expected values. + * @param actuals + * double array with actual values + */ + public static void assertArrayEquals(double[] expecteds, double[] actuals, double delta) { + assertArrayEquals(null, expecteds, actuals, delta); + } + + /** + * Asserts that two float arrays are equal. If they are not, an + * {@link AssertionError} is thrown with the given message. + * + * @param message + * the identifying message for the {@link AssertionError} (<code>null</code> + * okay) + * @param expecteds + * float array with expected values. + * @param actuals + * float array with actual values + */ + public static void assertArrayEquals(String message, float[] expecteds, + float[] actuals, float delta) throws ArrayComparisonFailure { + new InexactComparisonCriteria(delta).arrayEquals(message, expecteds, actuals); + } + + /** + * Asserts that two float arrays are equal. If they are not, an + * {@link AssertionError} is thrown. + * + * @param expecteds + * float array with expected values. + * @param actuals + * float array with actual values + */ + public static void assertArrayEquals(float[] expecteds, float[] actuals, float delta) { + assertArrayEquals(null, expecteds, actuals, delta); + } + + /** + * Asserts that two object arrays are equal. If they are not, an + * {@link AssertionError} is thrown with the given message. If + * <code>expecteds</code> and <code>actuals</code> are <code>null</code>, + * they are considered equal. + * + * @param message + * the identifying message for the {@link AssertionError} (<code>null</code> + * okay) + * @param expecteds + * Object array or array of arrays (multi-dimensional array) with + * expected values. + * @param actuals + * Object array or array of arrays (multi-dimensional array) with + * actual values + */ + private static void internalArrayEquals(String message, Object expecteds, + Object actuals) throws ArrayComparisonFailure { + new ExactComparisonCriteria().arrayEquals(message, expecteds, actuals); + } + + /** + * Asserts that two doubles or floats are equal to within a positive delta. + * If they are not, an {@link AssertionError} is thrown with the given + * message. If the expected value is infinity then the delta value is + * ignored. NaNs are considered equal: + * <code>assertEquals(Double.NaN, Double.NaN, *)</code> passes + * + * @param message + * the identifying message for the {@link AssertionError} (<code>null</code> + * okay) + * @param expected + * expected value + * @param actual + * the value to check against <code>expected</code> + * @param delta + * the maximum delta between <code>expected</code> and + * <code>actual</code> for which both numbers are still + * considered equal. + */ + static public void assertEquals(String message, double expected, + double actual, double delta) { + if (Double.compare(expected, actual) == 0) + return; + if (!(Math.abs(expected - actual) <= delta)) + failNotEquals(message, new Double(expected), new Double(actual)); + } + + /** + * Asserts that two longs are equal. If they are not, an + * {@link AssertionError} is thrown. + * + * @param expected + * expected long value. + * @param actual + * actual long value + */ + static public void assertEquals(long expected, long actual) { + assertEquals(null, expected, actual); + } + + /** + * Asserts that two longs are equal. If they are not, an + * {@link AssertionError} is thrown with the given message. + * + * @param message + * the identifying message for the {@link AssertionError} (<code>null</code> + * okay) + * @param expected + * long expected value. + * @param actual + * long actual value + */ + static public void assertEquals(String message, long expected, long actual) { + assertEquals(message, (Long) expected, (Long) actual); + } + + /** + * @deprecated Use + * <code>assertEquals(double expected, double actual, double delta)</code> + * instead + */ + @Deprecated + static public void assertEquals(double expected, double actual) { + assertEquals(null, expected, actual); + } + + /** + * @deprecated Use + * <code>assertEquals(String message, double expected, double actual, double delta)</code> + * instead + */ + @Deprecated + static public void assertEquals(String message, double expected, + double actual) { + fail("Use assertEquals(expected, actual, delta) to compare floating-point numbers"); + } + + /** + * Asserts that two doubles or floats are equal to within a positive delta. + * If they are not, an {@link AssertionError} is thrown. If the expected + * value is infinity then the delta value is ignored.NaNs are considered + * equal: <code>assertEquals(Double.NaN, Double.NaN, *)</code> passes + * + * @param expected + * expected value + * @param actual + * the value to check against <code>expected</code> + * @param delta + * the maximum delta between <code>expected</code> and + * <code>actual</code> for which both numbers are still + * considered equal. + */ + static public void assertEquals(double expected, double actual, double delta) { + assertEquals(null, expected, actual, delta); + } + + /** + * Asserts that an object isn't null. If it is an {@link AssertionError} is + * thrown with the given message. + * + * @param message + * the identifying message for the {@link AssertionError} (<code>null</code> + * okay) + * @param object + * Object to check or <code>null</code> + */ + static public void assertNotNull(String message, Object object) { + assertTrue(message, object != null); + } + + /** + * Asserts that an object isn't null. If it is an {@link AssertionError} is + * thrown. + * + * @param object + * Object to check or <code>null</code> + */ + static public void assertNotNull(Object object) { + assertNotNull(null, object); + } + + /** + * Asserts that an object is null. If it is not, an {@link AssertionError} + * is thrown with the given message. + * + * @param message + * the identifying message for the {@link AssertionError} (<code>null</code> + * okay) + * @param object + * Object to check or <code>null</code> + */ + static public void assertNull(String message, Object object) { + assertTrue(message, object == null); + } + + /** + * Asserts that an object is null. If it isn't an {@link AssertionError} is + * thrown. + * + * @param object + * Object to check or <code>null</code> + */ + static public void assertNull(Object object) { + assertNull(null, object); + } + + /** + * Asserts that two objects refer to the same object. If they are not, an + * {@link AssertionError} is thrown with the given message. + * + * @param message + * the identifying message for the {@link AssertionError} (<code>null</code> + * okay) + * @param expected + * the expected object + * @param actual + * the object to compare to <code>expected</code> + */ + static public void assertSame(String message, Object expected, Object actual) { + if (expected == actual) + return; + failNotSame(message, expected, actual); + } + + /** + * Asserts that two objects refer to the same object. If they are not the + * same, an {@link AssertionError} without a message is thrown. + * + * @param expected + * the expected object + * @param actual + * the object to compare to <code>expected</code> + */ + static public void assertSame(Object expected, Object actual) { + assertSame(null, expected, actual); + } + + /** + * Asserts that two objects do not refer to the same object. If they do + * refer to the same object, an {@link AssertionError} is thrown with the + * given message. + * + * @param message + * the identifying message for the {@link AssertionError} (<code>null</code> + * okay) + * @param unexpected + * the object you don't expect + * @param actual + * the object to compare to <code>unexpected</code> + */ + static public void assertNotSame(String message, Object unexpected, + Object actual) { + if (unexpected == actual) + failSame(message); + } + + /** + * Asserts that two objects do not refer to the same object. If they do + * refer to the same object, an {@link AssertionError} without a message is + * thrown. + * + * @param unexpected + * the object you don't expect + * @param actual + * the object to compare to <code>unexpected</code> + */ + static public void assertNotSame(Object unexpected, Object actual) { + assertNotSame(null, unexpected, actual); + } + + static private void failSame(String message) { + String formatted= ""; + if (message != null) + formatted= message + " "; + fail(formatted + "expected not same"); + } + + static private void failNotSame(String message, Object expected, + Object actual) { + String formatted= ""; + if (message != null) + formatted= message + " "; + fail(formatted + "expected same:<" + expected + "> was not:<" + actual + + ">"); + } + + static private void failNotEquals(String message, Object expected, + Object actual) { + fail(format(message, expected, actual)); + } + + static String format(String message, Object expected, Object actual) { + String formatted= ""; + if (message != null && !message.equals("")) + formatted= message + " "; + String expectedString= String.valueOf(expected); + String actualString= String.valueOf(actual); + if (expectedString.equals(actualString)) + return formatted + "expected: " + + formatClassAndValue(expected, expectedString) + + " but was: " + formatClassAndValue(actual, actualString); + else + return formatted + "expected:<" + expectedString + "> but was:<" + + actualString + ">"; + } + + private static String formatClassAndValue(Object value, String valueString) { + String className= value == null ? "null" : value.getClass().getName(); + return className + "<" + valueString + ">"; + } + + /** + * Asserts that two object arrays are equal. If they are not, an + * {@link AssertionError} is thrown with the given message. If + * <code>expecteds</code> and <code>actuals</code> are <code>null</code>, + * they are considered equal. + * + * @param message + * the identifying message for the {@link AssertionError} (<code>null</code> + * okay) + * @param expecteds + * Object array or array of arrays (multi-dimensional array) with + * expected values. + * @param actuals + * Object array or array of arrays (multi-dimensional array) with + * actual values + * @deprecated use assertArrayEquals + */ + @Deprecated + public static void assertEquals(String message, Object[] expecteds, + Object[] actuals) { + assertArrayEquals(message, expecteds, actuals); + } + + /** + * Asserts that two object arrays are equal. If they are not, an + * {@link AssertionError} is thrown. If <code>expected</code> and + * <code>actual</code> are <code>null</code>, they are considered + * equal. + * + * @param expecteds + * Object array or array of arrays (multi-dimensional array) with + * expected values + * @param actuals + * Object array or array of arrays (multi-dimensional array) with + * actual values + * @deprecated use assertArrayEquals + */ + @Deprecated + public static void assertEquals(Object[] expecteds, Object[] actuals) { + assertArrayEquals(expecteds, actuals); + } + + /** + * Asserts that <code>actual</code> satisfies the condition specified by + * <code>matcher</code>. If not, an {@link AssertionError} is thrown with + * information about the matcher and failing value. Example: + * + * <pre> + * assertThat(0, is(1)); // fails: + * // failure message: + * // expected: is <1> + * // got value: <0> + * assertThat(0, is(not(1))) // passes + * </pre> + * + * @param <T> + * the static type accepted by the matcher (this can flag obvious + * compile-time problems such as {@code assertThat(1, is("a"))} + * @param actual + * the computed value being compared + * @param matcher + * an expression, built of {@link Matcher}s, specifying allowed + * values + * + * @see org.hamcrest.CoreMatchers + * @see org.junit.matchers.JUnitMatchers + */ + public static <T> void assertThat(T actual, Matcher<T> matcher) { + assertThat("", actual, matcher); + } + + /** + * Asserts that <code>actual</code> satisfies the condition specified by + * <code>matcher</code>. If not, an {@link AssertionError} is thrown with + * the reason and information about the matcher and failing value. Example: + * + * <pre> + * : + * assertThat("Help! Integers don't work", 0, is(1)); // fails: + * // failure message: + * // Help! Integers don't work + * // expected: is <1> + * // got value: <0> + * assertThat("Zero is one", 0, is(not(1))) // passes + * </pre> + * + * @param reason + * additional information about the error + * @param <T> + * the static type accepted by the matcher (this can flag obvious + * compile-time problems such as {@code assertThat(1, is("a"))} + * @param actual + * the computed value being compared + * @param matcher + * an expression, built of {@link Matcher}s, specifying allowed + * values + * + * @see org.hamcrest.CoreMatchers + * @see org.junit.matchers.JUnitMatchers + */ + public static <T> void assertThat(String reason, T actual, + Matcher<T> matcher) { + if (!matcher.matches(actual)) { + Description description= new StringDescription(); + description.appendText(reason); + description.appendText("\nExpected: "); + description.appendDescriptionOf(matcher); + description.appendText("\n got: "); + description.appendValue(actual); + description.appendText("\n"); + throw new java.lang.AssertionError(description.toString()); + } + } +} diff --git a/src/org/junit/Assume.java b/src/org/junit/Assume.java new file mode 100644 index 0000000..7b6c21a --- /dev/null +++ b/src/org/junit/Assume.java @@ -0,0 +1,94 @@ +package org.junit; + +import static java.util.Arrays.asList; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.hamcrest.CoreMatchers.nullValue; +import org.hamcrest.Matcher; +import org.junit.internal.AssumptionViolatedException; +import org.junit.internal.matchers.Each; + +/** + * A set of methods useful for stating assumptions about the conditions in which a test is meaningful. + * A failed assumption does not mean the code is broken, but that the test provides no useful information. + * The default JUnit runner treats tests with failing assumptions as ignored. Custom runners may behave differently. + * + * For example: + * <pre> + * // only provides information if database is reachable. + * \@Test public void calculateTotalSalary() { + * DBConnection dbc = Database.connect(); + * assumeNotNull(dbc); + * // ... + * } + * </pre> + * These methods can be used directly: <code>Assume.assumeTrue(...)</code>, however, they + * read better if they are referenced through static import:<br/> + * <pre> + * import static org.junit.Assume.*; + * ... + * assumeTrue(...); + * </pre> + */ +public class Assume { + /** + * If called with an expression evaluating to {@code false}, the test will halt and be ignored. + * @param b + */ + public static void assumeTrue(boolean b) { + assumeThat(b, is(true)); + } + + /** + * If called with one or more null elements in <code>objects</code>, the test will halt and be ignored. + * @param objects + */ + public static void assumeNotNull(Object... objects) { + assumeThat(asList(objects), Each.each(notNullValue())); + } + + /** + * Call to assume that <code>actual</code> satisfies the condition specified by <code>matcher</code>. + * If not, the test halts and is ignored. + * Example: + * <pre>: + * assumeThat(1, is(1)); // passes + * foo(); // will execute + * assumeThat(0, is(1)); // assumption failure! test halts + * int x = 1 / 0; // will never execute + * </pre> + * + * @param <T> the static type accepted by the matcher (this can flag obvious compile-time problems such as {@code assumeThat(1, is("a"))} + * @param actual the computed value being compared + * @param matcher an expression, built of {@link Matcher}s, specifying allowed values + * + * @see org.hamcrest.CoreMatchers + * @see org.junit.matchers.JUnitMatchers + */ + public static <T> void assumeThat(T actual, Matcher<T> matcher) { + if (!matcher.matches(actual)) + throw new AssumptionViolatedException(actual, matcher); + } + + /** + * Use to assume that an operation completes normally. If {@code t} is non-null, the test will halt and be ignored. + * + * For example: + * <pre> + * \@Test public void parseDataFile() { + * DataFile file; + * try { + * file = DataFile.open("sampledata.txt"); + * } catch (IOException e) { + * // stop test and ignore if data can't be opened + * assumeNoException(e); + * } + * // ... + * } + * </pre> + * @param t if non-null, the offending exception + */ + public static void assumeNoException(Throwable t) { + assumeThat(t, nullValue()); + } +} diff --git a/src/org/junit/Before.java b/src/org/junit/Before.java new file mode 100644 index 0000000..66b34ee --- /dev/null +++ b/src/org/junit/Before.java @@ -0,0 +1,39 @@ +package org.junit; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * <p>When writing tests, it is common to find that several tests need similar + * objects created before they can run. Annotating a <code>public void</code> method + * with <code>@Before</code> causes that method to be run before the {@link org.junit.Test} method. + * The <code>@Before</code> methods of superclasses will be run before those of the current class. + * No other ordering is defined. + * </p> + * + * Here is a simple example: + * <pre> + * public class Example { + * List empty; + * @Before public void initialize() { + * empty= new ArrayList(); + * } + * @Test public void size() { + * ... + * } + * @Test public void remove() { + * ... + * } + * } + * </pre> + * + * @see org.junit.BeforeClass + * @see org.junit.After + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface Before { +} + diff --git a/src/org/junit/BeforeClass.java b/src/org/junit/BeforeClass.java new file mode 100644 index 0000000..35b7854 --- /dev/null +++ b/src/org/junit/BeforeClass.java @@ -0,0 +1,35 @@ +package org.junit; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * <p>Sometimes several tests need to share computationally expensive setup + * (like logging into a database). While this can compromise the independence of + * tests, sometimes it is a necessary optimization. Annotating a <code>public static void</code> no-arg method + * with <code>@BeforeClass</code> causes it to be run once before any of + * the test methods in the class. The <code>@BeforeClass</code> methods of superclasses + * will be run before those the current class.</p> + * + * For example: + * <pre> + * public class Example { + * @BeforeClass public static void onlyOnce() { + * ... + * } + * @Test public void one() { + * ... + * } + * @Test public void two() { + * ... + * } + * } + * </pre> + * @see org.junit.AfterClass + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface BeforeClass { +} diff --git a/src/org/junit/ClassRule.java b/src/org/junit/ClassRule.java new file mode 100644 index 0000000..97a111f --- /dev/null +++ b/src/org/junit/ClassRule.java @@ -0,0 +1,60 @@ +package org.junit; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Annotates static fields that contain rules. Such a field must be public, + * static, and a subtype of {@link org.junit.rules.TestRule}. + * The {@link org.junit.runners.model.Statement} passed + * to the {@link org.junit.rules.TestRule} will run any {@link BeforeClass} methods, + * then the entire body of the test class (all contained methods, if it is + * a standard JUnit test class, or all contained classes, if it is a + * {@link org.junit.runners.Suite}), and finally any {@link AfterClass} methods. + * + * The statement passed to the {@link org.junit.rules.TestRule} will never throw an exception, + * and throwing an exception from the {@link org.junit.rules.TestRule} will result in undefined + * behavior. This means that some {@link org.junit.rules.TestRule}s, such as + * {@link org.junit.rules.ErrorCollector}, + * {@link org.junit.rules.ExpectedException}, + * and {@link org.junit.rules.Timeout}, + * have undefined behavior when used as {@link ClassRule}s. + * + * If there are multiple + * annotated {@link ClassRule}s on a class, they will be applied in an order + * that depends on your JVM's implementation of the reflection API, which is + * undefined, in general. + * + * For example, here is a test suite that connects to a server once before + * all the test classes run, and disconnects after they are finished: + * + * <pre> + * + * @RunWith(Suite.class) + * @SuiteClasses({A.class, B.class, C.class}) + * public class UsesExternalResource { + * public static Server myServer= new Server(); + * + * @ClassRule + * public static ExternalResource resource= new ExternalResource() { + * @Override + * protected void before() throws Throwable { + * myServer.connect(); + * }; + * + * @Override + * protected void after() { + * myServer.disconnect(); + * }; + * }; + * } + * </pre> + * + * For more information and more examples, see {@link org.junit.rules.TestRule}. + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.FIELD}) +public @interface ClassRule { +} diff --git a/src/org/junit/ComparisonFailure.java b/src/org/junit/ComparisonFailure.java new file mode 100644 index 0000000..d37db4f --- /dev/null +++ b/src/org/junit/ComparisonFailure.java @@ -0,0 +1,138 @@ +package org.junit; + +/** + * Thrown when an {@link org.junit.Assert#assertEquals(Object, Object) assertEquals(String, String)} fails. Create and throw + * a <code>ComparisonFailure</code> manually if you want to show users the difference between two complex + * strings. + * + * Inspired by a patch from Alex Chaffee (alex@purpletech.com) + */ +public class ComparisonFailure extends AssertionError { + /** + * The maximum length for fExpected and fActual. If it is exceeded, the strings should be shortened. + * @see ComparisonCompactor + */ + private static final int MAX_CONTEXT_LENGTH= 20; + private static final long serialVersionUID= 1L; + + private String fExpected; + private String fActual; + + /** + * Constructs a comparison failure. + * @param message the identifying message or null + * @param expected the expected string value + * @param actual the actual string value + */ + public ComparisonFailure (String message, String expected, String actual) { + super (message); + fExpected= expected; + fActual= actual; + } + + /** + * Returns "..." in place of common prefix and "..." in + * place of common suffix between expected and actual. + * + * @see Throwable#getMessage() + */ + @Override + public String getMessage() { + return new ComparisonCompactor(MAX_CONTEXT_LENGTH, fExpected, fActual).compact(super.getMessage()); + } + + /** + * Returns the actual string value + * @return the actual string value + */ + public String getActual() { + return fActual; + } + /** + * Returns the expected string value + * @return the expected string value + */ + public String getExpected() { + return fExpected; + } + + private static class ComparisonCompactor { + private static final String ELLIPSIS= "..."; + private static final String DELTA_END= "]"; + private static final String DELTA_START= "["; + + /** + * The maximum length for <code>expected</code> and <code>actual</code>. When <code>contextLength</code> + * is exceeded, the Strings are shortened + */ + private int fContextLength; + private String fExpected; + private String fActual; + private int fPrefix; + private int fSuffix; + + /** + * @param contextLength the maximum length for <code>expected</code> and <code>actual</code>. When contextLength + * is exceeded, the Strings are shortened + * @param expected the expected string value + * @param actual the actual string value + */ + public ComparisonCompactor(int contextLength, String expected, String actual) { + fContextLength= contextLength; + fExpected= expected; + fActual= actual; + } + + private String compact(String message) { + if (fExpected == null || fActual == null || areStringsEqual()) + return Assert.format(message, fExpected, fActual); + + findCommonPrefix(); + findCommonSuffix(); + String expected= compactString(fExpected); + String actual= compactString(fActual); + return Assert.format(message, expected, actual); + } + + private String compactString(String source) { + String result= DELTA_START + source.substring(fPrefix, source.length() - fSuffix + 1) + DELTA_END; + if (fPrefix > 0) + result= computeCommonPrefix() + result; + if (fSuffix > 0) + result= result + computeCommonSuffix(); + return result; + } + + private void findCommonPrefix() { + fPrefix= 0; + int end= Math.min(fExpected.length(), fActual.length()); + for (; fPrefix < end; fPrefix++) { + if (fExpected.charAt(fPrefix) != fActual.charAt(fPrefix)) + break; + } + } + + private void findCommonSuffix() { + int expectedSuffix= fExpected.length() - 1; + int actualSuffix= fActual.length() - 1; + for (; actualSuffix >= fPrefix && expectedSuffix >= fPrefix; actualSuffix--, expectedSuffix--) { + if (fExpected.charAt(expectedSuffix) != fActual.charAt(actualSuffix)) + break; + } + fSuffix= fExpected.length() - expectedSuffix; + } + + private String computeCommonPrefix() { + return (fPrefix > fContextLength ? ELLIPSIS : "") + fExpected.substring(Math.max(0, fPrefix - fContextLength), fPrefix); + } + + private String computeCommonSuffix() { + int end= Math.min(fExpected.length() - fSuffix + 1 + fContextLength, fExpected.length()); + return fExpected.substring(fExpected.length() - fSuffix + 1, end) + (fExpected.length() - fSuffix + 1 < fExpected.length() - fContextLength ? ELLIPSIS : ""); + } + + private boolean areStringsEqual() { + return fExpected.equals(fActual); + } + } +}
\ No newline at end of file diff --git a/src/org/junit/Ignore.java b/src/org/junit/Ignore.java new file mode 100644 index 0000000..de530a9 --- /dev/null +++ b/src/org/junit/Ignore.java @@ -0,0 +1,39 @@ +package org.junit; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * <p>Sometimes you want to temporarily disable a test or a group of tests. Methods annotated with + * {@link org.junit.Test} that are also annotated with <code>@Ignore</code> will not be executed as tests. + * Also, you can annotate a class containing test methods with <code>@Ignore</code> and none of the containing + * tests will be executed. Native JUnit 4 test runners should report the number of ignored tests along with the + * number of tests that ran and the number of tests that failed.</p> + * + * For example: + * <pre> + * @Ignore @Test public void something() { ... + * </pre> + * @Ignore takes an optional default parameter if you want to record why a test is being ignored:<br/> + * <pre> + * @Ignore("not ready yet") @Test public void something() { ... + * </pre> + * @Ignore can also be applied to the test class:<br/> + * <pre> + * @Ignore public class IgnoreMe { + * @Test public void test1() { ... } + * @Test public void test2() { ... } + * } + * </pre> + * + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.METHOD, ElementType.TYPE}) +public @interface Ignore { + /** + * The optional reason why the test is ignored. + */ + String value() default ""; +} diff --git a/src/org/junit/Rule.java b/src/org/junit/Rule.java new file mode 100644 index 0000000..9e67c07 --- /dev/null +++ b/src/org/junit/Rule.java @@ -0,0 +1,47 @@ +package org.junit; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Annotates fields that contain rules. Such a field must be public, not + * static, and a subtype of {@link org.junit.rules.TestRule}. + * The {@link org.junit.runners.model.Statement} passed + * to the {@link org.junit.rules.TestRule} will run any {@link Before} methods, + * then the {@link Test} method, and finally any {@link After} methods, + * throwing an exception if any of these fail. If there are multiple + * annotated {@link Rule}s on a class, they will be applied in an order + * that depends on your JVM's implementation of the reflection API, which is + * undefined, in general. + * + * For example, here is a test class that creates a temporary folder before + * each test method, and deletes it after each: + * + * <pre> + * public static class HasTempFolder { + * @Rule + * public TemporaryFolder folder= new TemporaryFolder(); + * + * @Test + * public void testUsingTempFolder() throws IOException { + * File createdFile= folder.newFile("myfile.txt"); + * File createdFolder= folder.newFolder("subfolder"); + * // ... + * } + * } + * </pre> + * + * For more information and more examples, see + * {@link org.junit.rules.TestRule}. + * + * Note: for backwards compatibility, this annotation may also mark + * fields of type {@link org.junit.rules.MethodRule}, which will be honored. However, + * this is a deprecated interface and feature. + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.FIELD}) +public @interface Rule { + +}
\ No newline at end of file diff --git a/src/org/junit/Test.java b/src/org/junit/Test.java new file mode 100644 index 0000000..23dc78a --- /dev/null +++ b/src/org/junit/Test.java @@ -0,0 +1,68 @@ +package org.junit; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * <p>The <code>Test</code> annotation tells JUnit that the <code>public void</code> method + * to which it is attached can be run as a test case. To run the method, + * JUnit first constructs a fresh instance of the class then invokes the + * annotated method. Any exceptions thrown by the test will be reported + * by JUnit as a failure. If no exceptions are thrown, the test is assumed + * to have succeeded.</p> + * + * <p>A simple test looks like this: + * <pre> + * public class Example { + * <b>@Test</b> + * public void method() { + * org.junit.Assert.assertTrue( new ArrayList().isEmpty() ); + * } + * } + * </pre> + * </p> + * + * <p>The <code>Test</code> annotation supports two optional parameters. + * The first, <code>expected</code>, declares that a test method should throw + * an exception. If it doesn't throw an exception or if it throws a different exception + * than the one declared, the test fails. For example, the following test succeeds: + * <pre> + * @Test(<b>expected=IndexOutOfBoundsException.class</b>) public void outOfBounds() { + * new ArrayList<Object>().get(1); + * } + * </pre></p> + * + * <p>The second optional parameter, <code>timeout</code>, causes a test to fail if it takes + * longer than a specified amount of clock time (measured in milliseconds). The following test fails: + * <pre> + * @Test(<b>timeout=100</b>) public void infinity() { + * while(true); + * } + * </pre></p> + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.METHOD}) +public @interface Test { + + /** + * Default empty exception + */ + static class None extends Throwable { + private static final long serialVersionUID= 1L; + private None() { + } + } + + /** + * Optionally specify <code>expected</code>, a Throwable, to cause a test method to succeed iff + * an exception of the specified class is thrown by the method. + */ + Class<? extends Throwable> expected() default None.class; + + /** + * Optionally specify <code>timeout</code> in milliseconds to cause a test method to fail if it + * takes longer than that number of milliseconds.*/ + long timeout() default 0L; +} diff --git a/src/org/junit/experimental/ParallelComputer.java b/src/org/junit/experimental/ParallelComputer.java new file mode 100644 index 0000000..fccb97c --- /dev/null +++ b/src/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/org/junit/experimental/categories/Categories.java b/src/org/junit/experimental/categories/Categories.java new file mode 100644 index 0000000..d57b4d3 --- /dev/null +++ b/src/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 { + * @Test + * public void a() { + * fail(); + * } + * + * @Category(SlowTests.class) + * @Test + * public void b() { + * } + * } + * + * @Category( { SlowTests.class, FastTests.class }) + * public static class B { + * @Test + * public void c() { + * + * } + * } + * + * @RunWith(Categories.class) + * @IncludeCategory(SlowTests.class) + * @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/org/junit/experimental/categories/Category.java b/src/org/junit/experimental/categories/Category.java new file mode 100644 index 0000000..3a4c0b9 --- /dev/null +++ b/src/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 { + @Test + public void a() { + fail(); + } + + @Category(SlowTests.class) + @Test + public void b() { + } + } + + @Category({SlowTests.class, FastTests.class}) + public static class B { + @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/org/junit/experimental/max/CouldNotReadCoreException.java b/src/org/junit/experimental/max/CouldNotReadCoreException.java new file mode 100644 index 0000000..03c3c8c --- /dev/null +++ b/src/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/org/junit/experimental/max/MaxCore.java b/src/org/junit/experimental/max/MaxCore.java new file mode 100644 index 0000000..a2a34a9 --- /dev/null +++ b/src/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/org/junit/experimental/max/MaxHistory.java b/src/org/junit/experimental/max/MaxHistory.java new file mode 100644 index 0000000..e091793 --- /dev/null +++ b/src/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/org/junit/experimental/results/FailureList.java b/src/org/junit/experimental/results/FailureList.java new file mode 100644 index 0000000..f4bc9b7 --- /dev/null +++ b/src/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/org/junit/experimental/results/PrintableResult.java b/src/org/junit/experimental/results/PrintableResult.java new file mode 100644 index 0000000..8bc6f54 --- /dev/null +++ b/src/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/org/junit/experimental/results/ResultMatchers.java b/src/org/junit/experimental/results/ResultMatchers.java new file mode 100644 index 0000000..220d0dc --- /dev/null +++ b/src/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/org/junit/experimental/runners/Enclosed.java b/src/org/junit/experimental/runners/Enclosed.java new file mode 100644 index 0000000..b0560ed --- /dev/null +++ b/src/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/org/junit/experimental/theories/DataPoint.java b/src/org/junit/experimental/theories/DataPoint.java new file mode 100644 index 0000000..2aaba6a --- /dev/null +++ b/src/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/org/junit/experimental/theories/DataPoints.java b/src/org/junit/experimental/theories/DataPoints.java new file mode 100644 index 0000000..42145e3 --- /dev/null +++ b/src/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/org/junit/experimental/theories/ParameterSignature.java b/src/org/junit/experimental/theories/ParameterSignature.java new file mode 100644 index 0000000..e7150fc --- /dev/null +++ b/src/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/org/junit/experimental/theories/ParameterSupplier.java b/src/org/junit/experimental/theories/ParameterSupplier.java new file mode 100644 index 0000000..9016c91 --- /dev/null +++ b/src/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/org/junit/experimental/theories/ParametersSuppliedBy.java b/src/org/junit/experimental/theories/ParametersSuppliedBy.java new file mode 100644 index 0000000..8f090ef --- /dev/null +++ b/src/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/org/junit/experimental/theories/PotentialAssignment.java b/src/org/junit/experimental/theories/PotentialAssignment.java new file mode 100644 index 0000000..0c008d0 --- /dev/null +++ b/src/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/org/junit/experimental/theories/Theories.java b/src/org/junit/experimental/theories/Theories.java new file mode 100644 index 0000000..82ff98b --- /dev/null +++ b/src/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/org/junit/experimental/theories/Theory.java b/src/org/junit/experimental/theories/Theory.java new file mode 100644 index 0000000..134fe9d --- /dev/null +++ b/src/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/org/junit/experimental/theories/internal/AllMembersSupplier.java b/src/org/junit/experimental/theories/internal/AllMembersSupplier.java new file mode 100644 index 0000000..615cc3e --- /dev/null +++ b/src/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/org/junit/experimental/theories/internal/Assignments.java b/src/org/junit/experimental/theories/internal/Assignments.java new file mode 100644 index 0000000..bd94f00 --- /dev/null +++ b/src/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/org/junit/experimental/theories/internal/ParameterizedAssertionError.java b/src/org/junit/experimental/theories/internal/ParameterizedAssertionError.java new file mode 100644 index 0000000..285bc3a --- /dev/null +++ b/src/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/org/junit/experimental/theories/suppliers/TestedOn.java b/src/org/junit/experimental/theories/suppliers/TestedOn.java new file mode 100644 index 0000000..d6ede64 --- /dev/null +++ b/src/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/org/junit/experimental/theories/suppliers/TestedOnSupplier.java b/src/org/junit/experimental/theories/suppliers/TestedOnSupplier.java new file mode 100644 index 0000000..f80298f --- /dev/null +++ b/src/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; + } +} diff --git a/src/org/junit/internal/ArrayComparisonFailure.java b/src/org/junit/internal/ArrayComparisonFailure.java new file mode 100644 index 0000000..08851de --- /dev/null +++ b/src/org/junit/internal/ArrayComparisonFailure.java @@ -0,0 +1,59 @@ +package org.junit.internal; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.Assert; + +/** + * Thrown when two array elements differ + * @see Assert#assertArrayEquals(String, Object[], Object[]) + */ +public class ArrayComparisonFailure extends AssertionError { + + private static final long serialVersionUID= 1L; + + private List<Integer> fIndices= new ArrayList<Integer>(); + private final String fMessage; + private final AssertionError fCause; + + /** + * Construct a new <code>ArrayComparisonFailure</code> with an error text and the array's + * dimension that was not equal + * @param cause the exception that caused the array's content to fail the assertion test + * @param index the array position of the objects that are not equal. + * @see Assert#assertArrayEquals(String, Object[], Object[]) + */ + public ArrayComparisonFailure(String message, AssertionError cause, int index) { + fMessage= message; + fCause= cause; + addDimension(index); + } + + public void addDimension(int index) { + fIndices.add(0, index); + } + + @Override + public String getMessage() { + StringBuilder builder= new StringBuilder(); + if (fMessage != null) + builder.append(fMessage); + builder.append("arrays first differed at element "); + for (int each : fIndices) { + builder.append("["); + builder.append(each); + builder.append("]"); + } + builder.append("; "); + builder.append(fCause.getMessage()); + return builder.toString(); + } + + /** + * {@inheritDoc} + */ + @Override public String toString() { + return getMessage(); + } +} diff --git a/src/org/junit/internal/AssumptionViolatedException.java b/src/org/junit/internal/AssumptionViolatedException.java new file mode 100644 index 0000000..8e11268 --- /dev/null +++ b/src/org/junit/internal/AssumptionViolatedException.java @@ -0,0 +1,40 @@ +package org.junit.internal; + +import org.hamcrest.Description; +import org.hamcrest.Matcher; +import org.hamcrest.SelfDescribing; +import org.hamcrest.StringDescription; + +public class AssumptionViolatedException extends RuntimeException implements SelfDescribing { + private static final long serialVersionUID= 1L; + + private final Object fValue; + + private final Matcher<?> fMatcher; + + public AssumptionViolatedException(Object value, Matcher<?> matcher) { + super(value instanceof Throwable ? (Throwable) value : null); + fValue= value; + fMatcher= matcher; + } + + public AssumptionViolatedException(String assumption) { + this(assumption, null); + } + + @Override + public String getMessage() { + return StringDescription.asString(this); + } + + public void describeTo(Description description) { + if (fMatcher != null) { + description.appendText("got: "); + description.appendValue(fValue); + description.appendText(", expected: "); + description.appendDescriptionOf(fMatcher); + } else { + description.appendText("failed assumption: " + fValue); + } + } +}
\ No newline at end of file diff --git a/src/org/junit/internal/ComparisonCriteria.java b/src/org/junit/internal/ComparisonCriteria.java new file mode 100644 index 0000000..e97011d --- /dev/null +++ b/src/org/junit/internal/ComparisonCriteria.java @@ -0,0 +1,76 @@ +package org.junit.internal; + +import java.lang.reflect.Array; + +import org.junit.Assert; + +/** + * Defines criteria for finding two items "equal enough". Concrete subclasses + * may demand exact equality, or, for example, equality within a given delta. + */ +public abstract class ComparisonCriteria { + /** + * Asserts that two arrays are equal, according to the criteria defined by + * the concrete subclass. If they are not, an {@link AssertionError} is + * thrown with the given message. If <code>expecteds</code> and + * <code>actuals</code> are <code>null</code>, they are considered equal. + * + * @param message + * the identifying message for the {@link AssertionError} ( + * <code>null</code> okay) + * @param expecteds + * Object array or array of arrays (multi-dimensional array) with + * expected values. + * @param actuals + * Object array or array of arrays (multi-dimensional array) with + * actual values + */ + public void arrayEquals(String message, Object expecteds, Object actuals) + throws ArrayComparisonFailure { + if (expecteds == actuals) + return; + String header= message == null ? "" : message + ": "; + + int expectedsLength= assertArraysAreSameLength(expecteds, + actuals, header); + + for (int i= 0; i < expectedsLength; i++) { + Object expected= Array.get(expecteds, i); + Object actual= Array.get(actuals, i); + + if (isArray(expected) && isArray(actual)) { + try { + arrayEquals(message, expected, actual); + } catch (ArrayComparisonFailure e) { + e.addDimension(i); + throw e; + } + } else + try { + assertElementsEqual(expected, actual); + } catch (AssertionError e) { + throw new ArrayComparisonFailure(header, e, i); + } + } + } + + private boolean isArray(Object expected) { + return expected != null && expected.getClass().isArray(); + } + + private int assertArraysAreSameLength(Object expecteds, + Object actuals, String header) { + if (expecteds == null) + Assert.fail(header + "expected array was null"); + if (actuals == null) + Assert.fail(header + "actual array was null"); + int actualsLength= Array.getLength(actuals); + int expectedsLength= Array.getLength(expecteds); + if (actualsLength != expectedsLength) + Assert.fail(header + "array lengths differed, expected.length=" + + expectedsLength + " actual.length=" + actualsLength); + return expectedsLength; + } + + protected abstract void assertElementsEqual(Object expected, Object actual); +} diff --git a/src/org/junit/internal/ExactComparisonCriteria.java b/src/org/junit/internal/ExactComparisonCriteria.java new file mode 100644 index 0000000..0a632ff --- /dev/null +++ b/src/org/junit/internal/ExactComparisonCriteria.java @@ -0,0 +1,10 @@ +package org.junit.internal; + +import org.junit.Assert; + +public class ExactComparisonCriteria extends ComparisonCriteria { + @Override + protected void assertElementsEqual(Object expected, Object actual) { + Assert.assertEquals(expected, actual); + } +} diff --git a/src/org/junit/internal/InexactComparisonCriteria.java b/src/org/junit/internal/InexactComparisonCriteria.java new file mode 100644 index 0000000..ef3d7ff --- /dev/null +++ b/src/org/junit/internal/InexactComparisonCriteria.java @@ -0,0 +1,19 @@ +package org.junit.internal; + +import org.junit.Assert; + +public class InexactComparisonCriteria extends ComparisonCriteria { + public double fDelta; + + public InexactComparisonCriteria(double delta) { + fDelta= delta; + } + + @Override + protected void assertElementsEqual(Object expected, Object actual) { + if (expected instanceof Double) + Assert.assertEquals((Double)expected, (Double)actual, fDelta); + else + Assert.assertEquals((Float)expected, (Float)actual, fDelta); + } +}
\ No newline at end of file diff --git a/src/org/junit/internal/JUnitSystem.java b/src/org/junit/internal/JUnitSystem.java new file mode 100644 index 0000000..6d9c242 --- /dev/null +++ b/src/org/junit/internal/JUnitSystem.java @@ -0,0 +1,8 @@ +package org.junit.internal; + +import java.io.PrintStream; + +public interface JUnitSystem { + void exit(int i); + PrintStream out(); +} diff --git a/src/org/junit/internal/RealSystem.java b/src/org/junit/internal/RealSystem.java new file mode 100644 index 0000000..1067c6d --- /dev/null +++ b/src/org/junit/internal/RealSystem.java @@ -0,0 +1,15 @@ +package org.junit.internal; + +import java.io.PrintStream; + +public class RealSystem implements JUnitSystem { + + public void exit(int code) { + System.exit(code); + } + + public PrintStream out() { + return System.out; + } + +} diff --git a/src/org/junit/internal/TextListener.java b/src/org/junit/internal/TextListener.java new file mode 100644 index 0000000..2b1c679 --- /dev/null +++ b/src/org/junit/internal/TextListener.java @@ -0,0 +1,98 @@ +package org.junit.internal; + +import java.io.PrintStream; +import java.text.NumberFormat; +import java.util.List; + +import org.junit.runner.Description; +import org.junit.runner.Result; +import org.junit.runner.notification.Failure; +import org.junit.runner.notification.RunListener; + +public class TextListener extends RunListener { + + private final PrintStream fWriter; + + public TextListener(JUnitSystem system) { + this(system.out()); + } + + public TextListener(PrintStream writer) { + this.fWriter= writer; + } + + @Override + public void testRunFinished(Result result) { + printHeader(result.getRunTime()); + printFailures(result); + printFooter(result); + } + + @Override + public void testStarted(Description description) { + fWriter.append('.'); + } + + @Override + public void testFailure(Failure failure) { + fWriter.append('E'); + } + + @Override + public void testIgnored(Description description) { + fWriter.append('I'); + } + + /* + * Internal methods + */ + + private PrintStream getWriter() { + return fWriter; + } + + protected void printHeader(long runTime) { + getWriter().println(); + getWriter().println("Time: " + elapsedTimeAsString(runTime)); + } + + protected void printFailures(Result result) { + List<Failure> failures= result.getFailures(); + if (failures.size() == 0) + return; + if (failures.size() == 1) + getWriter().println("There was " + failures.size() + " failure:"); + else + getWriter().println("There were " + failures.size() + " failures:"); + int i= 1; + for (Failure each : failures) + printFailure(each, "" + i++); + } + + protected void printFailure(Failure each, String prefix) { + getWriter().println(prefix + ") " + each.getTestHeader()); + getWriter().print(each.getTrace()); + } + + protected void printFooter(Result result) { + if (result.wasSuccessful()) { + getWriter().println(); + getWriter().print("OK"); + getWriter().println(" (" + result.getRunCount() + " test" + (result.getRunCount() == 1 ? "" : "s") + ")"); + + } else { + getWriter().println(); + getWriter().println("FAILURES!!!"); + getWriter().println("Tests run: " + result.getRunCount() + ", Failures: " + result.getFailureCount()); + } + getWriter().println(); + } + + /** + * Returns the formatted string of the elapsed time. Duplicated from + * BaseTestRunner. Fix it. + */ + protected String elapsedTimeAsString(long runTime) { + return NumberFormat.getInstance().format((double) runTime / 1000); + } +} diff --git a/src/org/junit/internal/builders/AllDefaultPossibilitiesBuilder.java b/src/org/junit/internal/builders/AllDefaultPossibilitiesBuilder.java new file mode 100644 index 0000000..d3bd50a --- /dev/null +++ b/src/org/junit/internal/builders/AllDefaultPossibilitiesBuilder.java @@ -0,0 +1,57 @@ +/** + * + */ +package org.junit.internal.builders; + +import java.util.Arrays; +import java.util.List; + +import org.junit.runner.Runner; +import org.junit.runners.model.RunnerBuilder; + +public class AllDefaultPossibilitiesBuilder extends RunnerBuilder { + private final boolean fCanUseSuiteMethod; + + public AllDefaultPossibilitiesBuilder(boolean canUseSuiteMethod) { + fCanUseSuiteMethod= canUseSuiteMethod; + } + + @Override + public Runner runnerForClass(Class<?> testClass) throws Throwable { + List<RunnerBuilder> builders= Arrays.asList( + ignoredBuilder(), + annotatedBuilder(), + suiteMethodBuilder(), + junit3Builder(), + junit4Builder()); + + for (RunnerBuilder each : builders) { + Runner runner= each.safeRunnerForClass(testClass); + if (runner != null) + return runner; + } + return null; + } + + protected JUnit4Builder junit4Builder() { + return new JUnit4Builder(); + } + + protected JUnit3Builder junit3Builder() { + return new JUnit3Builder(); + } + + protected AnnotatedBuilder annotatedBuilder() { + return new AnnotatedBuilder(this); + } + + protected IgnoredBuilder ignoredBuilder() { + return new IgnoredBuilder(); + } + + protected RunnerBuilder suiteMethodBuilder() { + if (fCanUseSuiteMethod) + return new SuiteMethodBuilder(); + return new NullBuilder(); + } +}
\ No newline at end of file diff --git a/src/org/junit/internal/builders/AnnotatedBuilder.java b/src/org/junit/internal/builders/AnnotatedBuilder.java new file mode 100644 index 0000000..8ed9ca7 --- /dev/null +++ b/src/org/junit/internal/builders/AnnotatedBuilder.java @@ -0,0 +1,45 @@ +/** + * + */ +package org.junit.internal.builders; + +import org.junit.runner.RunWith; +import org.junit.runner.Runner; +import org.junit.runners.model.InitializationError; +import org.junit.runners.model.RunnerBuilder; + +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)"; + + private RunnerBuilder fSuiteBuilder; + + public AnnotatedBuilder(RunnerBuilder suiteBuilder) { + fSuiteBuilder= suiteBuilder; + } + + @Override + public Runner runnerForClass(Class<?> testClass) throws Exception { + RunWith annotation= testClass.getAnnotation(RunWith.class); + if (annotation != null) + return buildRunner(annotation.value(), testClass); + return null; + } + + public Runner buildRunner(Class<? extends Runner> runnerClass, + Class<?> testClass) throws Exception { + try { + return runnerClass.getConstructor(Class.class).newInstance( + new Object[] { testClass }); + } catch (NoSuchMethodException e) { + try { + return runnerClass.getConstructor(Class.class, + RunnerBuilder.class).newInstance( + new Object[] { testClass, fSuiteBuilder }); + } catch (NoSuchMethodException e2) { + String simpleName= runnerClass.getSimpleName(); + throw new InitializationError(String.format( + CONSTRUCTOR_ERROR_FORMAT, simpleName, simpleName)); + } + } + } +}
\ No newline at end of file diff --git a/src/org/junit/internal/builders/IgnoredBuilder.java b/src/org/junit/internal/builders/IgnoredBuilder.java new file mode 100644 index 0000000..6be342c --- /dev/null +++ b/src/org/junit/internal/builders/IgnoredBuilder.java @@ -0,0 +1,17 @@ +/** + * + */ +package org.junit.internal.builders; + +import org.junit.Ignore; +import org.junit.runner.Runner; +import org.junit.runners.model.RunnerBuilder; + +public class IgnoredBuilder extends RunnerBuilder { + @Override + public Runner runnerForClass(Class<?> testClass) { + if (testClass.getAnnotation(Ignore.class) != null) + return new IgnoredClassRunner(testClass); + return null; + } +}
\ No newline at end of file diff --git a/src/org/junit/internal/builders/IgnoredClassRunner.java b/src/org/junit/internal/builders/IgnoredClassRunner.java new file mode 100644 index 0000000..b4200a8 --- /dev/null +++ b/src/org/junit/internal/builders/IgnoredClassRunner.java @@ -0,0 +1,26 @@ +/** + * + */ +package org.junit.internal.builders; + +import org.junit.runner.Description; +import org.junit.runner.Runner; +import org.junit.runner.notification.RunNotifier; + +public class IgnoredClassRunner extends Runner { + private final Class<?> fTestClass; + + public IgnoredClassRunner(Class<?> testClass) { + fTestClass= testClass; + } + + @Override + public void run(RunNotifier notifier) { + notifier.fireTestIgnored(getDescription()); + } + + @Override + public Description getDescription() { + return Description.createSuiteDescription(fTestClass); + } +}
\ No newline at end of file diff --git a/src/org/junit/internal/builders/JUnit3Builder.java b/src/org/junit/internal/builders/JUnit3Builder.java new file mode 100644 index 0000000..ddb070b --- /dev/null +++ b/src/org/junit/internal/builders/JUnit3Builder.java @@ -0,0 +1,21 @@ +/** + * + */ +package org.junit.internal.builders; + +import org.junit.internal.runners.JUnit38ClassRunner; +import org.junit.runner.Runner; +import org.junit.runners.model.RunnerBuilder; + +public class JUnit3Builder extends RunnerBuilder { + @Override + public Runner runnerForClass(Class<?> testClass) throws Throwable { + if (isPre4Test(testClass)) + return new JUnit38ClassRunner(testClass); + return null; + } + + boolean isPre4Test(Class<?> testClass) { + return junit.framework.TestCase.class.isAssignableFrom(testClass); + } +}
\ No newline at end of file diff --git a/src/org/junit/internal/builders/JUnit4Builder.java b/src/org/junit/internal/builders/JUnit4Builder.java new file mode 100644 index 0000000..4380db7 --- /dev/null +++ b/src/org/junit/internal/builders/JUnit4Builder.java @@ -0,0 +1,15 @@ +/** + * + */ +package org.junit.internal.builders; + +import org.junit.runner.Runner; +import org.junit.runners.BlockJUnit4ClassRunner; +import org.junit.runners.model.RunnerBuilder; + +public class JUnit4Builder extends RunnerBuilder { + @Override + public Runner runnerForClass(Class<?> testClass) throws Throwable { + return new BlockJUnit4ClassRunner(testClass); + } +}
\ No newline at end of file diff --git a/src/org/junit/internal/builders/NullBuilder.java b/src/org/junit/internal/builders/NullBuilder.java new file mode 100644 index 0000000..9d43d69 --- /dev/null +++ b/src/org/junit/internal/builders/NullBuilder.java @@ -0,0 +1,14 @@ +/** + * + */ +package org.junit.internal.builders; + +import org.junit.runner.Runner; +import org.junit.runners.model.RunnerBuilder; + +public class NullBuilder extends RunnerBuilder { + @Override + public Runner runnerForClass(Class<?> each) throws Throwable { + return null; + } +}
\ No newline at end of file diff --git a/src/org/junit/internal/builders/SuiteMethodBuilder.java b/src/org/junit/internal/builders/SuiteMethodBuilder.java new file mode 100644 index 0000000..659bf31 --- /dev/null +++ b/src/org/junit/internal/builders/SuiteMethodBuilder.java @@ -0,0 +1,26 @@ +/** + * + */ +package org.junit.internal.builders; + +import org.junit.internal.runners.SuiteMethod; +import org.junit.runner.Runner; +import org.junit.runners.model.RunnerBuilder; + +public class SuiteMethodBuilder extends RunnerBuilder { + @Override + public Runner runnerForClass(Class<?> each) throws Throwable { + if (hasSuiteMethod(each)) + return new SuiteMethod(each); + return null; + } + + public boolean hasSuiteMethod(Class<?> testClass) { + try { + testClass.getMethod("suite"); + } catch (NoSuchMethodException e) { + return false; + } + return true; + } +}
\ No newline at end of file diff --git a/src/org/junit/internal/matchers/CombinableMatcher.java b/src/org/junit/internal/matchers/CombinableMatcher.java new file mode 100644 index 0000000..e9e6947 --- /dev/null +++ b/src/org/junit/internal/matchers/CombinableMatcher.java @@ -0,0 +1,34 @@ +package org.junit.internal.matchers; + +import static org.hamcrest.CoreMatchers.allOf; +import static org.hamcrest.CoreMatchers.anyOf; +import org.hamcrest.BaseMatcher; +import org.hamcrest.Description; +import org.hamcrest.Matcher; + +public class CombinableMatcher<T> extends BaseMatcher<T> { + + private final Matcher<? extends T> fMatcher; + + public CombinableMatcher(Matcher<? extends T> matcher) { + fMatcher= matcher; + } + + public boolean matches(Object item) { + return fMatcher.matches(item); + } + + public void describeTo(Description description) { + description.appendDescriptionOf(fMatcher); + } + + @SuppressWarnings("unchecked") + public CombinableMatcher<T> and(Matcher<? extends T> matcher) { + return new CombinableMatcher<T>(allOf(matcher, fMatcher)); + } + + @SuppressWarnings("unchecked") + public CombinableMatcher<T> or(Matcher<? extends T> matcher) { + return new CombinableMatcher<T>(anyOf(matcher, fMatcher)); + } +}
\ No newline at end of file diff --git a/src/org/junit/internal/matchers/Each.java b/src/org/junit/internal/matchers/Each.java new file mode 100644 index 0000000..527db3b --- /dev/null +++ b/src/org/junit/internal/matchers/Each.java @@ -0,0 +1,24 @@ +package org.junit.internal.matchers; + +import static org.hamcrest.CoreMatchers.not; +import static org.junit.internal.matchers.IsCollectionContaining.hasItem; +import org.hamcrest.BaseMatcher; +import org.hamcrest.Description; +import org.hamcrest.Matcher; + +public class Each { + public static <T> Matcher<Iterable<T>> each(final Matcher<T> individual) { + final Matcher<Iterable<T>> allItemsAre = not(hasItem(not(individual))); + + return new BaseMatcher<Iterable<T>>() { + public boolean matches(Object item) { + return allItemsAre.matches(item); + } + + public void describeTo(Description description) { + description.appendText("each "); + individual.describeTo(description); + } + }; + } +} diff --git a/src/org/junit/internal/matchers/IsCollectionContaining.java b/src/org/junit/internal/matchers/IsCollectionContaining.java new file mode 100644 index 0000000..4436a83 --- /dev/null +++ b/src/org/junit/internal/matchers/IsCollectionContaining.java @@ -0,0 +1,67 @@ +package org.junit.internal.matchers; + +import static org.hamcrest.core.AllOf.allOf; +import static org.hamcrest.core.IsEqual.equalTo; + +import java.util.ArrayList; +import java.util.Collection; + +import org.hamcrest.Description; +import org.hamcrest.Factory; +import org.hamcrest.Matcher; + +// Copied (hopefully temporarily) from hamcrest-library +public class IsCollectionContaining<T> extends TypeSafeMatcher<Iterable<T>> { + private final Matcher<? extends T> elementMatcher; + + public IsCollectionContaining(Matcher<? extends T> elementMatcher) { + this.elementMatcher = elementMatcher; + } + + @Override + public boolean matchesSafely(Iterable<T> collection) { + for (T item : collection) { + if (elementMatcher.matches(item)){ + return true; + } + } + return false; + } + + public void describeTo(Description description) { + description + .appendText("a collection containing ") + .appendDescriptionOf(elementMatcher); + } + + @Factory + public static <T> Matcher<Iterable<T>> hasItem(Matcher<? extends T> elementMatcher) { + return new IsCollectionContaining<T>(elementMatcher); + } + + @Factory + public static <T> Matcher<Iterable<T>> hasItem(T element) { + return hasItem(equalTo(element)); + } + + @Factory + public static <T> Matcher<Iterable<T>> hasItems(Matcher<? extends T>... elementMatchers) { + Collection<Matcher<? extends Iterable<T>>> all + = new ArrayList<Matcher<? extends Iterable<T>>>(elementMatchers.length); + for (Matcher<? extends T> elementMatcher : elementMatchers) { + all.add(hasItem(elementMatcher)); + } + return allOf(all); + } + + @Factory + public static <T> Matcher<Iterable<T>> hasItems(T... elements) { + Collection<Matcher<? extends Iterable<T>>> all + = new ArrayList<Matcher<? extends Iterable<T>>>(elements.length); + for (T element : elements) { + all.add(hasItem(element)); + } + return allOf(all); + } + +} diff --git a/src/org/junit/internal/matchers/StringContains.java b/src/org/junit/internal/matchers/StringContains.java new file mode 100644 index 0000000..e5f5334 --- /dev/null +++ b/src/org/junit/internal/matchers/StringContains.java @@ -0,0 +1,31 @@ +/* Copyright (c) 2000-2006 hamcrest.org + */ +package org.junit.internal.matchers; + +import org.hamcrest.Factory; +import org.hamcrest.Matcher; + +/** + * Tests if the argument is a string that contains a substring. + */ +public class StringContains extends SubstringMatcher { + public StringContains(String substring) { + super(substring); + } + + @Override + protected boolean evalSubstringOf(String s) { + return s.indexOf(substring) >= 0; + } + + @Override + protected String relationship() { + return "containing"; + } + + @Factory + public static Matcher<String> containsString(String substring) { + return new StringContains(substring); + } + +}
\ No newline at end of file diff --git a/src/org/junit/internal/matchers/SubstringMatcher.java b/src/org/junit/internal/matchers/SubstringMatcher.java new file mode 100644 index 0000000..1c65240 --- /dev/null +++ b/src/org/junit/internal/matchers/SubstringMatcher.java @@ -0,0 +1,28 @@ +package org.junit.internal.matchers; + +import org.hamcrest.Description; + +public abstract class SubstringMatcher extends TypeSafeMatcher<String> { + + protected final String substring; + + protected SubstringMatcher(final String substring) { + this.substring = substring; + } + + @Override + public boolean matchesSafely(String item) { + return evalSubstringOf(item); + } + + public void describeTo(Description description) { + description.appendText("a string ") + .appendText(relationship()) + .appendText(" ") + .appendValue(substring); + } + + protected abstract boolean evalSubstringOf(String string); + + protected abstract String relationship(); +}
\ No newline at end of file diff --git a/src/org/junit/internal/matchers/TypeSafeMatcher.java b/src/org/junit/internal/matchers/TypeSafeMatcher.java new file mode 100644 index 0000000..794a174 --- /dev/null +++ b/src/org/junit/internal/matchers/TypeSafeMatcher.java @@ -0,0 +1,60 @@ +package org.junit.internal.matchers; + +import java.lang.reflect.Method; + +import org.hamcrest.BaseMatcher; + +/** + * Convenient base class for Matchers that require a non-null value of a specific type. + * This simply implements the null check, checks the type and then casts. + * + * @author Joe Walnes + */ +public abstract class TypeSafeMatcher<T> extends BaseMatcher<T> { + + private Class<?> expectedType; + + /** + * Subclasses should implement this. The item will already have been checked for + * the specific type and will never be null. + */ + public abstract boolean matchesSafely(T item); + + protected TypeSafeMatcher() { + expectedType = findExpectedType(getClass()); + } + + private static Class<?> findExpectedType(Class<?> fromClass) { + for (Class<?> c = fromClass; c != Object.class; c = c.getSuperclass()) { + for (Method method : c.getDeclaredMethods()) { + if (isMatchesSafelyMethod(method)) { + return method.getParameterTypes()[0]; + } + } + } + + throw new Error("Cannot determine correct type for matchesSafely() method."); + } + + private static boolean isMatchesSafelyMethod(Method method) { + return method.getName().equals("matchesSafely") + && method.getParameterTypes().length == 1 + && !method.isSynthetic(); + } + + protected TypeSafeMatcher(Class<T> expectedType) { + this.expectedType = expectedType; + } + + /** + * Method made final to prevent accidental override. + * If you need to override this, there's no point on extending TypeSafeMatcher. + * Instead, extend the {@link BaseMatcher}. + */ + @SuppressWarnings({"unchecked"}) + public final boolean matches(Object item) { + return item != null + && expectedType.isInstance(item) + && matchesSafely((T) item); + } +} diff --git a/src/org/junit/internal/requests/ClassRequest.java b/src/org/junit/internal/requests/ClassRequest.java new file mode 100644 index 0000000..53bf520 --- /dev/null +++ b/src/org/junit/internal/requests/ClassRequest.java @@ -0,0 +1,26 @@ +package org.junit.internal.requests; + + +import org.junit.internal.builders.AllDefaultPossibilitiesBuilder; +import org.junit.runner.Request; +import org.junit.runner.Runner; + +public class ClassRequest extends Request { + private final Class<?> fTestClass; + + private boolean fCanUseSuiteMethod; + + public ClassRequest(Class<?> testClass, boolean canUseSuiteMethod) { + fTestClass= testClass; + fCanUseSuiteMethod= canUseSuiteMethod; + } + + public ClassRequest(Class<?> testClass) { + this(testClass, true); + } + + @Override + public Runner getRunner() { + return new AllDefaultPossibilitiesBuilder(fCanUseSuiteMethod).safeRunnerForClass(fTestClass); + } +}
\ No newline at end of file diff --git a/src/org/junit/internal/requests/FilterRequest.java b/src/org/junit/internal/requests/FilterRequest.java new file mode 100644 index 0000000..e5d98d1 --- /dev/null +++ b/src/org/junit/internal/requests/FilterRequest.java @@ -0,0 +1,42 @@ +/** + * + */ +package org.junit.internal.requests; + +import org.junit.internal.runners.ErrorReportingRunner; +import org.junit.runner.Request; +import org.junit.runner.Runner; +import org.junit.runner.manipulation.Filter; +import org.junit.runner.manipulation.NoTestsRemainException; + +/** + * A filtered {@link Request}. + */ +public final class FilterRequest extends Request { + private final Request fRequest; + private final Filter fFilter; + + /** + * Creates a filtered Request + * @param classRequest a {@link Request} describing your Tests + * @param filter {@link Filter} to apply to the Tests described in + * <code>classRequest</code> + */ + public FilterRequest(Request classRequest, Filter filter) { + fRequest= classRequest; + fFilter= filter; + } + + @Override + public Runner getRunner() { + try { + Runner runner= fRequest.getRunner(); + fFilter.apply(runner); + return runner; + } catch (NoTestsRemainException e) { + return new ErrorReportingRunner(Filter.class, new Exception(String + .format("No tests found matching %s from %s", fFilter + .describe(), fRequest.toString()))); + } + } +}
\ No newline at end of file diff --git a/src/org/junit/internal/requests/SortingRequest.java b/src/org/junit/internal/requests/SortingRequest.java new file mode 100644 index 0000000..3c6f4f5 --- /dev/null +++ b/src/org/junit/internal/requests/SortingRequest.java @@ -0,0 +1,25 @@ +package org.junit.internal.requests; + +import java.util.Comparator; + +import org.junit.runner.Description; +import org.junit.runner.Request; +import org.junit.runner.Runner; +import org.junit.runner.manipulation.Sorter; + +public class SortingRequest extends Request { + private final Request fRequest; + private final Comparator<Description> fComparator; + + public SortingRequest(Request request, Comparator<Description> comparator) { + fRequest= request; + fComparator= comparator; + } + + @Override + public Runner getRunner() { + Runner runner= fRequest.getRunner(); + new Sorter(fComparator).apply(runner); + return runner; + } +} diff --git a/src/org/junit/internal/requests/package-info.java b/src/org/junit/internal/requests/package-info.java new file mode 100644 index 0000000..66d2928 --- /dev/null +++ b/src/org/junit/internal/requests/package-info.java @@ -0,0 +1,6 @@ +/** + * Provides implementations of {@link org.junit.runner.Request}. + * + * @since 4.0 + */ +package org.junit.internal.requests;
\ No newline at end of file diff --git a/src/org/junit/internal/runners/ClassRoadie.java b/src/org/junit/internal/runners/ClassRoadie.java new file mode 100644 index 0000000..1f77d37 --- /dev/null +++ b/src/org/junit/internal/runners/ClassRoadie.java @@ -0,0 +1,79 @@ +package org.junit.internal.runners; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.List; + +import org.junit.runner.Description; +import org.junit.runner.notification.Failure; +import org.junit.runner.notification.RunNotifier; +import org.junit.runners.BlockJUnit4ClassRunner; + +/** + * @deprecated Included for backwards compatibility with JUnit 4.4. Will be + * removed in the next release. Please use + * {@link BlockJUnit4ClassRunner} in place of {@link JUnit4ClassRunner}. + */ +@Deprecated +public +class ClassRoadie { + private RunNotifier fNotifier; + private TestClass fTestClass; + private Description fDescription; + private final Runnable fRunnable; + + public ClassRoadie(RunNotifier notifier, TestClass testClass, + Description description, Runnable runnable) { + fNotifier= notifier; + fTestClass= testClass; + fDescription= description; + fRunnable= runnable; + } + + protected void runUnprotected() { + fRunnable.run(); + }; + + protected void addFailure(Throwable targetException) { + fNotifier.fireTestFailure(new Failure(fDescription, targetException)); + } + + public void runProtected() { + try { + runBefores(); + runUnprotected(); + } catch (FailedBefore e) { + } finally { + runAfters(); + } + } + + private void runBefores() throws FailedBefore { + try { + try { + List<Method> befores= fTestClass.getBefores(); + for (Method before : befores) + before.invoke(null); + } catch (InvocationTargetException e) { + throw e.getTargetException(); + } + } catch (org.junit.internal.AssumptionViolatedException e) { + throw new FailedBefore(); + } catch (Throwable e) { + addFailure(e); + throw new FailedBefore(); + } + } + + private void runAfters() { + List<Method> afters= fTestClass.getAfters(); + for (Method after : afters) + try { + after.invoke(null); + } catch (InvocationTargetException e) { + addFailure(e.getTargetException()); + } catch (Throwable e) { + addFailure(e); // Untested, but seems impossible + } + } +}
\ No newline at end of file diff --git a/src/org/junit/internal/runners/ErrorReportingRunner.java b/src/org/junit/internal/runners/ErrorReportingRunner.java new file mode 100644 index 0000000..200b6f0 --- /dev/null +++ b/src/org/junit/internal/runners/ErrorReportingRunner.java @@ -0,0 +1,60 @@ +package org.junit.internal.runners; + +import java.lang.reflect.InvocationTargetException; +import java.util.Arrays; +import java.util.List; + +import org.junit.runner.Description; +import org.junit.runner.Runner; +import org.junit.runner.notification.Failure; +import org.junit.runner.notification.RunNotifier; +import org.junit.runners.model.InitializationError; + +public class ErrorReportingRunner extends Runner { + private final List<Throwable> fCauses; + + private final Class<?> fTestClass; + + public ErrorReportingRunner(Class<?> testClass, Throwable cause) { + fTestClass= testClass; + fCauses= getCauses(cause); + } + + @Override + public Description getDescription() { + Description description= Description.createSuiteDescription(fTestClass); + for (Throwable each : fCauses) + description.addChild(describeCause(each)); + return description; + } + + @Override + public void run(RunNotifier notifier) { + for (Throwable each : fCauses) + runCause(each, notifier); + } + + @SuppressWarnings("deprecation") + private List<Throwable> getCauses(Throwable cause) { + if (cause instanceof InvocationTargetException) + return getCauses(cause.getCause()); + if (cause instanceof InitializationError) + return ((InitializationError) cause).getCauses(); + if (cause instanceof org.junit.internal.runners.InitializationError) + return ((org.junit.internal.runners.InitializationError) cause) + .getCauses(); + return Arrays.asList(cause); + } + + private Description describeCause(Throwable child) { + return Description.createTestDescription(fTestClass, + "initializationError"); + } + + private void runCause(Throwable child, RunNotifier notifier) { + Description description= describeCause(child); + notifier.fireTestStarted(description); + notifier.fireTestFailure(new Failure(description, child)); + notifier.fireTestFinished(description); + } +}
\ No newline at end of file diff --git a/src/org/junit/internal/runners/FailedBefore.java b/src/org/junit/internal/runners/FailedBefore.java new file mode 100644 index 0000000..29dcba4 --- /dev/null +++ b/src/org/junit/internal/runners/FailedBefore.java @@ -0,0 +1,14 @@ +package org.junit.internal.runners; + +import org.junit.runners.BlockJUnit4ClassRunner; + + +/** + * @deprecated Included for backwards compatibility with JUnit 4.4. Will be + * removed in the next release. Please use + * {@link BlockJUnit4ClassRunner} in place of {@link JUnit4ClassRunner}. + */ +@Deprecated +class FailedBefore extends Exception { + private static final long serialVersionUID= 1L; +}
\ No newline at end of file diff --git a/src/org/junit/internal/runners/InitializationError.java b/src/org/junit/internal/runners/InitializationError.java new file mode 100644 index 0000000..5715ec5 --- /dev/null +++ b/src/org/junit/internal/runners/InitializationError.java @@ -0,0 +1,30 @@ +package org.junit.internal.runners; + +import java.util.Arrays; +import java.util.List; + +@Deprecated +/** + * Use the published version: {@link org.junit.runners.InitializationError} + * This may disappear as soon as 1 April 2009 + */ +public class InitializationError extends Exception { + private static final long serialVersionUID= 1L; + private final List<Throwable> fErrors; + + public InitializationError(List<Throwable> errors) { + fErrors= errors; + } + + public InitializationError(Throwable... errors) { + this(Arrays.asList(errors)); + } + + public InitializationError(String string) { + this(new Exception(string)); + } + + public List<Throwable> getCauses() { + return fErrors; + } +} diff --git a/src/org/junit/internal/runners/JUnit38ClassRunner.java b/src/org/junit/internal/runners/JUnit38ClassRunner.java new file mode 100644 index 0000000..0028d0c --- /dev/null +++ b/src/org/junit/internal/runners/JUnit38ClassRunner.java @@ -0,0 +1,158 @@ +package org.junit.internal.runners; + +import junit.extensions.TestDecorator; +import junit.framework.AssertionFailedError; +import junit.framework.Test; +import junit.framework.TestCase; +import junit.framework.TestListener; +import junit.framework.TestResult; +import junit.framework.TestSuite; +import org.junit.runner.Describable; +import org.junit.runner.Description; +import org.junit.runner.Runner; +import org.junit.runner.manipulation.Filter; +import org.junit.runner.manipulation.Filterable; +import org.junit.runner.manipulation.NoTestsRemainException; +import org.junit.runner.manipulation.Sortable; +import org.junit.runner.manipulation.Sorter; +import org.junit.runner.notification.Failure; +import org.junit.runner.notification.RunNotifier; + +public class JUnit38ClassRunner extends Runner implements Filterable, Sortable { + private final class OldTestClassAdaptingListener implements + TestListener { + private final RunNotifier fNotifier; + + private OldTestClassAdaptingListener(RunNotifier notifier) { + fNotifier= notifier; + } + + public void endTest(Test test) { + fNotifier.fireTestFinished(asDescription(test)); + } + + public void startTest(Test test) { + fNotifier.fireTestStarted(asDescription(test)); + } + + // Implement junit.framework.TestListener + public void addError(Test test, Throwable t) { + Failure failure= new Failure(asDescription(test), t); + fNotifier.fireTestFailure(failure); + } + + private Description asDescription(Test test) { + if (test instanceof Describable) { + Describable facade= (Describable) test; + return facade.getDescription(); + } + return Description.createTestDescription(getEffectiveClass(test), getName(test)); + } + + private Class<? extends Test> getEffectiveClass(Test test) { + return test.getClass(); + } + + private String getName(Test test) { + if (test instanceof TestCase) + return ((TestCase) test).getName(); + else + return test.toString(); + } + + public void addFailure(Test test, AssertionFailedError t) { + addError(test, t); + } + } + + private Test fTest; + + public JUnit38ClassRunner(Class<?> klass) { + this(new TestSuite(klass.asSubclass(TestCase.class))); + } + + public JUnit38ClassRunner(Test test) { + super(); + setTest(test); + } + + @Override + public void run(RunNotifier notifier) { + TestResult result= new TestResult(); + result.addListener(createAdaptingListener(notifier)); + getTest().run(result); + } + + public TestListener createAdaptingListener(final RunNotifier notifier) { + return new OldTestClassAdaptingListener(notifier); + } + + @Override + public Description getDescription() { + return makeDescription(getTest()); + } + + private static Description makeDescription(Test test) { + if (test instanceof TestCase) { + TestCase tc= (TestCase) test; + return Description.createTestDescription(tc.getClass(), tc.getName()); + } else if (test instanceof TestSuite) { + TestSuite ts= (TestSuite) test; + String name= ts.getName() == null ? createSuiteDescription(ts) : ts.getName(); + Description description= Description.createSuiteDescription(name); + int n= ts.testCount(); + for (int i= 0; i < n; i++) { + Description made= makeDescription(ts.testAt(i)); + description.addChild(made); + } + return description; + } else if (test instanceof Describable) { + Describable adapter= (Describable) test; + return adapter.getDescription(); + } else if (test instanceof TestDecorator) { + TestDecorator decorator= (TestDecorator) test; + return makeDescription(decorator.getTest()); + } else { + // This is the best we can do in this case + return Description.createSuiteDescription(test.getClass()); + } + } + + private static String createSuiteDescription(TestSuite ts) { + int count= ts.countTestCases(); + String example = count == 0 ? "" : String.format(" [example: %s]", ts.testAt(0)); + return String.format("TestSuite with %s tests%s", count, example); + } + + public void filter(Filter filter) throws NoTestsRemainException { + if (getTest() instanceof Filterable) { + Filterable adapter= (Filterable) getTest(); + adapter.filter(filter); + } else if (getTest() instanceof TestSuite) { + TestSuite suite= (TestSuite) getTest(); + TestSuite filtered= new TestSuite(suite.getName()); + int n= suite.testCount(); + for (int i= 0; i < n; i++) { + Test test= suite.testAt(i); + if (filter.shouldRun(makeDescription(test))) + filtered.addTest(test); + } + setTest(filtered); + } + } + + public void sort(Sorter sorter) { + if (getTest() instanceof Sortable) { + Sortable adapter= (Sortable) getTest(); + adapter.sort(sorter); + } + } + + private void setTest(Test test) { + fTest = test; + } + + private Test getTest() { + return fTest; + } +} diff --git a/src/org/junit/internal/runners/JUnit4ClassRunner.java b/src/org/junit/internal/runners/JUnit4ClassRunner.java new file mode 100644 index 0000000..d732880 --- /dev/null +++ b/src/org/junit/internal/runners/JUnit4ClassRunner.java @@ -0,0 +1,145 @@ +package org.junit.internal.runners; + +import java.lang.annotation.Annotation; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Collections; +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; + +import org.junit.runner.Description; +import org.junit.runner.Runner; +import org.junit.runner.manipulation.Filter; +import org.junit.runner.manipulation.Filterable; +import org.junit.runner.manipulation.NoTestsRemainException; +import org.junit.runner.manipulation.Sortable; +import org.junit.runner.manipulation.Sorter; +import org.junit.runner.notification.Failure; +import org.junit.runner.notification.RunNotifier; +import org.junit.runners.BlockJUnit4ClassRunner; + +/** + * @deprecated Included for backwards compatibility with JUnit 4.4. Will be + * removed in the next release. Please use + * {@link BlockJUnit4ClassRunner} in place of {@link JUnit4ClassRunner}. + * + * This may disappear as soon as 1 April 2009 + */ +@Deprecated +public class JUnit4ClassRunner extends Runner implements Filterable, Sortable { + private final List<Method> fTestMethods; + private TestClass fTestClass; + + public JUnit4ClassRunner(Class<?> klass) throws InitializationError { + fTestClass= new TestClass(klass); + fTestMethods= getTestMethods(); + validate(); + } + + protected List<Method> getTestMethods() { + return fTestClass.getTestMethods(); + } + + protected void validate() throws InitializationError { + MethodValidator methodValidator= new MethodValidator(fTestClass); + methodValidator.validateMethodsForDefaultRunner(); + methodValidator.assertValid(); + } + + @Override + public void run(final RunNotifier notifier) { + new ClassRoadie(notifier, fTestClass, getDescription(), new Runnable() { + public void run() { + runMethods(notifier); + } + }).runProtected(); + } + + protected void runMethods(final RunNotifier notifier) { + for (Method method : fTestMethods) + invokeTestMethod(method, notifier); + } + + @Override + public Description getDescription() { + Description spec= Description.createSuiteDescription(getName(), classAnnotations()); + List<Method> testMethods= fTestMethods; + for (Method method : testMethods) + spec.addChild(methodDescription(method)); + return spec; + } + + protected Annotation[] classAnnotations() { + return fTestClass.getJavaClass().getAnnotations(); + } + + protected String getName() { + return getTestClass().getName(); + } + + protected Object createTest() throws Exception { + return getTestClass().getConstructor().newInstance(); + } + + protected void invokeTestMethod(Method method, RunNotifier notifier) { + Description description= methodDescription(method); + Object test; + try { + test= createTest(); + } catch (InvocationTargetException e) { + testAborted(notifier, description, e.getCause()); + return; + } catch (Exception e) { + testAborted(notifier, description, e); + return; + } + TestMethod testMethod= wrapMethod(method); + new MethodRoadie(test, testMethod, notifier, description).run(); + } + + private void testAborted(RunNotifier notifier, Description description, + Throwable e) { + notifier.fireTestStarted(description); + notifier.fireTestFailure(new Failure(description, e)); + notifier.fireTestFinished(description); + } + + protected TestMethod wrapMethod(Method method) { + return new TestMethod(method, fTestClass); + } + + protected String testName(Method method) { + return method.getName(); + } + + protected Description methodDescription(Method method) { + return Description.createTestDescription(getTestClass().getJavaClass(), testName(method), testAnnotations(method)); + } + + protected Annotation[] testAnnotations(Method method) { + return method.getAnnotations(); + } + + public void filter(Filter filter) throws NoTestsRemainException { + for (Iterator<Method> iter= fTestMethods.iterator(); iter.hasNext();) { + Method method= iter.next(); + if (!filter.shouldRun(methodDescription(method))) + iter.remove(); + } + if (fTestMethods.isEmpty()) + throw new NoTestsRemainException(); + } + + public void sort(final Sorter sorter) { + Collections.sort(fTestMethods, new Comparator<Method>() { + public int compare(Method o1, Method o2) { + return sorter.compare(methodDescription(o1), methodDescription(o2)); + } + }); + } + + protected TestClass getTestClass() { + return fTestClass; + } +}
\ No newline at end of file diff --git a/src/org/junit/internal/runners/MethodRoadie.java b/src/org/junit/internal/runners/MethodRoadie.java new file mode 100644 index 0000000..4407821 --- /dev/null +++ b/src/org/junit/internal/runners/MethodRoadie.java @@ -0,0 +1,157 @@ +package org.junit.internal.runners; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +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 java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +import org.junit.internal.AssumptionViolatedException; +import org.junit.runner.Description; +import org.junit.runner.notification.Failure; +import org.junit.runner.notification.RunNotifier; +import org.junit.runners.BlockJUnit4ClassRunner; + +/** + * @deprecated Included for backwards compatibility with JUnit 4.4. Will be + * removed in the next release. Please use + * {@link BlockJUnit4ClassRunner} in place of {@link JUnit4ClassRunner}. + */ +@Deprecated +public class MethodRoadie { + private final Object fTest; + private final RunNotifier fNotifier; + private final Description fDescription; + private TestMethod fTestMethod; + + public MethodRoadie(Object test, TestMethod method, RunNotifier notifier, Description description) { + fTest= test; + fNotifier= notifier; + fDescription= description; + fTestMethod= method; + } + + public void run() { + if (fTestMethod.isIgnored()) { + fNotifier.fireTestIgnored(fDescription); + return; + } + fNotifier.fireTestStarted(fDescription); + try { + long timeout= fTestMethod.getTimeout(); + if (timeout > 0) + runWithTimeout(timeout); + else + runTest(); + } finally { + fNotifier.fireTestFinished(fDescription); + } + } + + private void runWithTimeout(final long timeout) { + runBeforesThenTestThenAfters(new Runnable() { + + public void run() { + ExecutorService service= Executors.newSingleThreadExecutor(); + Callable<Object> callable= new Callable<Object>() { + public Object call() throws Exception { + runTestMethod(); + return null; + } + }; + Future<Object> result= service.submit(callable); + service.shutdown(); + try { + boolean terminated= service.awaitTermination(timeout, + TimeUnit.MILLISECONDS); + if (!terminated) + service.shutdownNow(); + result.get(0, TimeUnit.MILLISECONDS); // throws the exception if one occurred during the invocation + } catch (TimeoutException e) { + addFailure(new Exception(String.format("test timed out after %d milliseconds", timeout))); + } catch (Exception e) { + addFailure(e); + } + } + }); + } + + public void runTest() { + runBeforesThenTestThenAfters(new Runnable() { + public void run() { + runTestMethod(); + } + }); + } + + public void runBeforesThenTestThenAfters(Runnable test) { + try { + runBefores(); + test.run(); + } catch (FailedBefore e) { + } catch (Exception e) { + throw new RuntimeException("test should never throw an exception to this level"); + } finally { + runAfters(); + } + } + + protected void runTestMethod() { + try { + fTestMethod.invoke(fTest); + if (fTestMethod.expectsException()) + addFailure(new AssertionError("Expected exception: " + fTestMethod.getExpectedException().getName())); + } catch (InvocationTargetException e) { + Throwable actual= e.getTargetException(); + if (actual instanceof AssumptionViolatedException) + return; + else if (!fTestMethod.expectsException()) + addFailure(actual); + else if (fTestMethod.isUnexpected(actual)) { + String message= "Unexpected exception, expected<" + fTestMethod.getExpectedException().getName() + "> but was<" + + actual.getClass().getName() + ">"; + addFailure(new Exception(message, actual)); + } + } catch (Throwable e) { + addFailure(e); + } + } + + private void runBefores() throws FailedBefore { + try { + try { + List<Method> befores= fTestMethod.getBefores(); + for (Method before : befores) + before.invoke(fTest); + } catch (InvocationTargetException e) { + throw e.getTargetException(); + } + } catch (AssumptionViolatedException e) { + throw new FailedBefore(); + } catch (Throwable e) { + addFailure(e); + throw new FailedBefore(); + } + } + + private void runAfters() { + List<Method> afters= fTestMethod.getAfters(); + for (Method after : afters) + try { + after.invoke(fTest); + } catch (InvocationTargetException e) { + addFailure(e.getTargetException()); + } catch (Throwable e) { + addFailure(e); // Untested, but seems impossible + } + } + + protected void addFailure(Throwable e) { + fNotifier.fireTestFailure(new Failure(fDescription, e)); + } +} + diff --git a/src/org/junit/internal/runners/MethodValidator.java b/src/org/junit/internal/runners/MethodValidator.java new file mode 100644 index 0000000..cadc93f --- /dev/null +++ b/src/org/junit/internal/runners/MethodValidator.java @@ -0,0 +1,91 @@ +package org.junit.internal.runners; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.List; + +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runners.BlockJUnit4ClassRunner; + +/** + * @deprecated Included for backwards compatibility with JUnit 4.4. Will be + * removed in the next release. Please use + * {@link BlockJUnit4ClassRunner} in place of {@link JUnit4ClassRunner}. + */ +@Deprecated +public class MethodValidator { + + private final List<Throwable> fErrors= new ArrayList<Throwable>(); + + private TestClass fTestClass; + + public MethodValidator(TestClass testClass) { + fTestClass = testClass; + } + + public void validateInstanceMethods() { + validateTestMethods(After.class, false); + validateTestMethods(Before.class, false); + validateTestMethods(Test.class, false); + + List<Method> methods= fTestClass.getAnnotatedMethods(Test.class); + if (methods.size() == 0) + fErrors.add(new Exception("No runnable methods")); + } + + public void validateStaticMethods() { + validateTestMethods(BeforeClass.class, true); + validateTestMethods(AfterClass.class, true); + } + + public List<Throwable> validateMethodsForDefaultRunner() { + validateNoArgConstructor(); + validateStaticMethods(); + validateInstanceMethods(); + return fErrors; + } + + public void assertValid() throws InitializationError { + if (!fErrors.isEmpty()) + throw new InitializationError(fErrors); + } + + public void validateNoArgConstructor() { + try { + fTestClass.getConstructor(); + } catch (Exception e) { + fErrors.add(new Exception("Test class should have public zero-argument constructor", e)); + } + } + + private void validateTestMethods(Class<? extends Annotation> annotation, + boolean isStatic) { + List<Method> methods= fTestClass.getAnnotatedMethods(annotation); + + for (Method each : methods) { + if (Modifier.isStatic(each.getModifiers()) != isStatic) { + String state= isStatic ? "should" : "should not"; + fErrors.add(new Exception("Method " + each.getName() + "() " + + state + " be static")); + } + if (!Modifier.isPublic(each.getDeclaringClass().getModifiers())) + fErrors.add(new Exception("Class " + each.getDeclaringClass().getName() + + " should be public")); + if (!Modifier.isPublic(each.getModifiers())) + fErrors.add(new Exception("Method " + each.getName() + + " should be public")); + if (each.getReturnType() != Void.TYPE) + fErrors.add(new Exception("Method " + each.getName() + + " should be void")); + if (each.getParameterTypes().length != 0) + fErrors.add(new Exception("Method " + each.getName() + + " should have no parameters")); + } + } +} diff --git a/src/org/junit/internal/runners/SuiteMethod.java b/src/org/junit/internal/runners/SuiteMethod.java new file mode 100644 index 0000000..4e8bebc --- /dev/null +++ b/src/org/junit/internal/runners/SuiteMethod.java @@ -0,0 +1,40 @@ +package org.junit.internal.runners; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; + +import junit.framework.Test; + +/** Runner for use with JUnit 3.8.x-style AllTests classes + * (those that only implement a static <code>suite()</code> + * method). For example: + * <pre> + * @RunWith(AllTests.class) + * public class ProductTests { + * public static junit.framework.Test suite() { + * ... + * } + * } + * </pre> + */ +public class SuiteMethod extends JUnit38ClassRunner { + public SuiteMethod(Class<?> klass) throws Throwable { + super(testFromSuiteMethod(klass)); + } + + public static Test testFromSuiteMethod(Class<?> klass) throws Throwable { + Method suiteMethod= null; + Test suite= null; + try { + suiteMethod= klass.getMethod("suite"); + if (! Modifier.isStatic(suiteMethod.getModifiers())) { + throw new Exception(klass.getName() + ".suite() must be static"); + } + suite= (Test) suiteMethod.invoke(null); // static method + } catch (InvocationTargetException e) { + throw e.getCause(); + } + return suite; + } +} diff --git a/src/org/junit/internal/runners/TestClass.java b/src/org/junit/internal/runners/TestClass.java new file mode 100644 index 0000000..1ca2b9d --- /dev/null +++ b/src/org/junit/internal/runners/TestClass.java @@ -0,0 +1,102 @@ +package org.junit.internal.runners; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runners.BlockJUnit4ClassRunner; + +/** + * @deprecated Included for backwards compatibility with JUnit 4.4. Will be + * removed in the next release. Please use + * {@link BlockJUnit4ClassRunner} in place of {@link JUnit4ClassRunner}. + */ +@Deprecated +public class TestClass { + private final Class<?> fClass; + + public TestClass(Class<?> klass) { + fClass= klass; + } + + public List<Method> getTestMethods() { + return getAnnotatedMethods(Test.class); + } + + List<Method> getBefores() { + return getAnnotatedMethods(BeforeClass.class); + } + + List<Method> getAfters() { + return getAnnotatedMethods(AfterClass.class); + } + + public List<Method> getAnnotatedMethods(Class<? extends Annotation> annotationClass) { + List<Method> results= new ArrayList<Method>(); + for (Class<?> eachClass : getSuperClasses(fClass)) { + Method[] methods= eachClass.getDeclaredMethods(); + for (Method eachMethod : methods) { + Annotation annotation= eachMethod.getAnnotation(annotationClass); + if (annotation != null && ! isShadowed(eachMethod, results)) + results.add(eachMethod); + } + } + if (runsTopToBottom(annotationClass)) + Collections.reverse(results); + return results; + } + + private boolean runsTopToBottom(Class< ? extends Annotation> annotation) { + return annotation.equals(Before.class) || annotation.equals(BeforeClass.class); + } + + private boolean isShadowed(Method method, List<Method> results) { + for (Method each : results) { + if (isShadowed(method, each)) + return true; + } + return false; + } + + private boolean isShadowed(Method current, Method previous) { + if (! previous.getName().equals(current.getName())) + return false; + if (previous.getParameterTypes().length != current.getParameterTypes().length) + return false; + for (int i= 0; i < previous.getParameterTypes().length; i++) { + if (! previous.getParameterTypes()[i].equals(current.getParameterTypes()[i])) + return false; + } + return true; + } + + private List<Class<?>> getSuperClasses(Class< ?> testClass) { + ArrayList<Class<?>> results= new ArrayList<Class<?>>(); + Class<?> current= testClass; + while (current != null) { + results.add(current); + current= current.getSuperclass(); + } + return results; + } + + public Constructor<?> getConstructor() throws SecurityException, NoSuchMethodException { + return fClass.getConstructor(); + } + + public Class<?> getJavaClass() { + return fClass; + } + + public String getName() { + return fClass.getName(); + } + +} diff --git a/src/org/junit/internal/runners/TestMethod.java b/src/org/junit/internal/runners/TestMethod.java new file mode 100644 index 0000000..a06213c --- /dev/null +++ b/src/org/junit/internal/runners/TestMethod.java @@ -0,0 +1,69 @@ +package org.junit.internal.runners; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.List; + +import org.junit.After; +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; +import org.junit.Test.None; +import org.junit.runners.BlockJUnit4ClassRunner; + +/** + * @deprecated Included for backwards compatibility with JUnit 4.4. Will be + * removed in the next release. Please use + * {@link BlockJUnit4ClassRunner} in place of {@link JUnit4ClassRunner}. + */ +@Deprecated +public class TestMethod { + private final Method fMethod; + private TestClass fTestClass; + + public TestMethod(Method method, TestClass testClass) { + fMethod= method; + fTestClass= testClass; + } + + public boolean isIgnored() { + return fMethod.getAnnotation(Ignore.class) != null; + } + + public long getTimeout() { + Test annotation= fMethod.getAnnotation(Test.class); + if (annotation == null) + return 0; + long timeout= annotation.timeout(); + return timeout; + } + + protected Class<? extends Throwable> getExpectedException() { + Test annotation= fMethod.getAnnotation(Test.class); + if (annotation == null || annotation.expected() == None.class) + return null; + else + return annotation.expected(); + } + + boolean isUnexpected(Throwable exception) { + return ! getExpectedException().isAssignableFrom(exception.getClass()); + } + + boolean expectsException() { + return getExpectedException() != null; + } + + List<Method> getBefores() { + return fTestClass.getAnnotatedMethods(Before.class); + } + + List<Method> getAfters() { + return fTestClass.getAnnotatedMethods(After.class); + } + + public void invoke(Object test) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException { + fMethod.invoke(test); + } + +} diff --git a/src/org/junit/internal/runners/model/EachTestNotifier.java b/src/org/junit/internal/runners/model/EachTestNotifier.java new file mode 100644 index 0000000..a7d534c --- /dev/null +++ b/src/org/junit/internal/runners/model/EachTestNotifier.java @@ -0,0 +1,51 @@ +/** + * + */ +package org.junit.internal.runners.model; + +import org.junit.internal.AssumptionViolatedException; +import org.junit.runner.Description; +import org.junit.runner.notification.Failure; +import org.junit.runner.notification.RunNotifier; +import org.junit.runners.model.MultipleFailureException; + +public class EachTestNotifier { + private final RunNotifier fNotifier; + + private final Description fDescription; + + public EachTestNotifier(RunNotifier notifier, Description description) { + fNotifier= notifier; + fDescription= description; + } + + public void addFailure(Throwable targetException) { + if (targetException instanceof MultipleFailureException) { + addMultipleFailureException((MultipleFailureException) targetException); + } else { + fNotifier + .fireTestFailure(new Failure(fDescription, targetException)); + } + } + + private void addMultipleFailureException(MultipleFailureException mfe) { + for (Throwable each : mfe.getFailures()) + addFailure(each); + } + + public void addFailedAssumption(AssumptionViolatedException e) { + fNotifier.fireTestAssumptionFailed(new Failure(fDescription, e)); + } + + public void fireTestFinished() { + fNotifier.fireTestFinished(fDescription); + } + + public void fireTestStarted() { + fNotifier.fireTestStarted(fDescription); + } + + public void fireTestIgnored() { + fNotifier.fireTestIgnored(fDescription); + } +}
\ No newline at end of file diff --git a/src/org/junit/internal/runners/model/MultipleFailureException.java b/src/org/junit/internal/runners/model/MultipleFailureException.java new file mode 100644 index 0000000..3316806 --- /dev/null +++ b/src/org/junit/internal/runners/model/MultipleFailureException.java @@ -0,0 +1,12 @@ +package org.junit.internal.runners.model; + +import java.util.List; + +@Deprecated +public class MultipleFailureException extends org.junit.runners.model.MultipleFailureException { + private static final long serialVersionUID= 1L; + + public MultipleFailureException(List<Throwable> errors) { + super(errors); + } +} diff --git a/src/org/junit/internal/runners/model/ReflectiveCallable.java b/src/org/junit/internal/runners/model/ReflectiveCallable.java new file mode 100644 index 0000000..9150d90 --- /dev/null +++ b/src/org/junit/internal/runners/model/ReflectiveCallable.java @@ -0,0 +1,22 @@ +/** + * + */ +package org.junit.internal.runners.model; + +import java.lang.reflect.InvocationTargetException; + +/** + * When invoked, throws the exception from the reflected method, rather than + * wrapping it in an InvocationTargetException. + */ +public abstract class ReflectiveCallable { + public Object run() throws Throwable { + try { + return runReflectiveCall(); + } catch (InvocationTargetException e) { + throw e.getTargetException(); + } + } + + protected abstract Object runReflectiveCall() throws Throwable; +}
\ No newline at end of file diff --git a/src/org/junit/internal/runners/package-info.java b/src/org/junit/internal/runners/package-info.java new file mode 100644 index 0000000..5ab7e7b --- /dev/null +++ b/src/org/junit/internal/runners/package-info.java @@ -0,0 +1,6 @@ +/** + * Provides implementations of {@link org.junit.runner.Runner} + * + * @since 4.0 + */ +package org.junit.internal.runners;
\ No newline at end of file diff --git a/src/org/junit/internal/runners/rules/RuleFieldValidator.java b/src/org/junit/internal/runners/rules/RuleFieldValidator.java new file mode 100644 index 0000000..e7df8bf --- /dev/null +++ b/src/org/junit/internal/runners/rules/RuleFieldValidator.java @@ -0,0 +1,92 @@ +package org.junit.internal.runners.rules; + +import java.lang.annotation.Annotation; +import java.util.List; + +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.rules.TestRule; +import org.junit.runners.model.FrameworkField; +import org.junit.runners.model.TestClass; + +/** + * A RuleFieldValidator validates the rule fields of a + * {@link org.junit.runners.model.TestClass}. All reasons for rejecting the + * {@code TestClass} are written to a list of errors. + * + * There are two slightly different validators. The {@link #CLASS_RULE_VALIDATOR} + * validates fields with a {@link ClassRule} annotation and the + * {@link #RULE_VALIDATOR} validates fields with a {@link Rule} annotation. + */ +public enum RuleFieldValidator { + /** + * Validates fields with a {@link ClassRule} annotation. + */ + CLASS_RULE_VALIDATOR(ClassRule.class, true), + /** + * Validates fields with a {@link Rule} annotation. + */ + RULE_VALIDATOR(Rule.class, false); + + private final Class<? extends Annotation> fAnnotation; + + private final boolean fOnlyStaticFields; + + private RuleFieldValidator(Class<? extends Annotation> annotation, + boolean onlyStaticFields) { + this.fAnnotation= annotation; + this.fOnlyStaticFields= onlyStaticFields; + } + + /** + * Validate the {@link org.junit.runners.model.TestClass} and adds reasons + * for rejecting the class to a list of errors. + * @param target the {@code TestClass} to validate. + * @param errors the list of errors. + */ + public void validate(TestClass target, List<Throwable> errors) { + List<FrameworkField> fields= target.getAnnotatedFields(fAnnotation); + for (FrameworkField each : fields) + validateField(each, errors); + } + + private void validateField(FrameworkField field, List<Throwable> errors) { + optionallyValidateStatic(field, errors); + validatePublic(field, errors); + validateTestRuleOrMethodRule(field, errors); + } + + private void optionallyValidateStatic(FrameworkField field, + List<Throwable> errors) { + if (fOnlyStaticFields && !field.isStatic()) + addError(errors, field, "must be static."); + } + + private void validatePublic(FrameworkField field, List<Throwable> errors) { + if (!field.isPublic()) + addError(errors, field, "must be public."); + } + + private void validateTestRuleOrMethodRule(FrameworkField field, + List<Throwable> errors) { + if (!isMethodRule(field) && !isTestRule(field)) + addError(errors, field, "must implement MethodRule or TestRule."); + } + + private boolean isTestRule(FrameworkField target) { + return TestRule.class.isAssignableFrom(target.getType()); + } + + @SuppressWarnings("deprecation") + private boolean isMethodRule(FrameworkField target) { + return org.junit.rules.MethodRule.class.isAssignableFrom(target + .getType()); + } + + private void addError(List<Throwable> errors, FrameworkField field, + String suffix) { + String message= "The @" + fAnnotation.getSimpleName() + " '" + + field.getName() + "' " + suffix; + errors.add(new Exception(message)); + } +} diff --git a/src/org/junit/internal/runners/statements/ExpectException.java b/src/org/junit/internal/runners/statements/ExpectException.java new file mode 100644 index 0000000..ddfef07 --- /dev/null +++ b/src/org/junit/internal/runners/statements/ExpectException.java @@ -0,0 +1,38 @@ +/** + * + */ +package org.junit.internal.runners.statements; + +import org.junit.internal.AssumptionViolatedException; +import org.junit.runners.model.Statement; + +public class ExpectException extends Statement { + private Statement fNext; + private final Class<? extends Throwable> fExpected; + + public ExpectException(Statement next, Class<? extends Throwable> expected) { + fNext= next; + fExpected= expected; + } + + @Override + public void evaluate() throws Exception { + boolean complete = false; + try { + fNext.evaluate(); + complete = true; + } catch (AssumptionViolatedException e) { + throw e; + } catch (Throwable e) { + if (!fExpected.isAssignableFrom(e.getClass())) { + String message= "Unexpected exception, expected<" + + fExpected.getName() + "> but was<" + + e.getClass().getName() + ">"; + throw new Exception(message, e); + } + } + if (complete) + throw new AssertionError("Expected exception: " + + fExpected.getName()); + } +}
\ No newline at end of file diff --git a/src/org/junit/internal/runners/statements/Fail.java b/src/org/junit/internal/runners/statements/Fail.java new file mode 100644 index 0000000..e7d0d5c --- /dev/null +++ b/src/org/junit/internal/runners/statements/Fail.java @@ -0,0 +1,17 @@ +package org.junit.internal.runners.statements; + +import org.junit.runners.model.Statement; + + +public class Fail extends Statement { + private final Throwable fError; + + public Fail(Throwable e) { + fError= e; + } + + @Override + public void evaluate() throws Throwable { + throw fError; + } +} diff --git a/src/org/junit/internal/runners/statements/FailOnTimeout.java b/src/org/junit/internal/runners/statements/FailOnTimeout.java new file mode 100644 index 0000000..bff7c72 --- /dev/null +++ b/src/org/junit/internal/runners/statements/FailOnTimeout.java @@ -0,0 +1,71 @@ +/** + * + */ +package org.junit.internal.runners.statements; + +import org.junit.runners.model.Statement; + +public class FailOnTimeout extends Statement { + private final Statement fOriginalStatement; + + private final long fTimeout; + + public FailOnTimeout(Statement originalStatement, long timeout) { + fOriginalStatement= originalStatement; + fTimeout= timeout; + } + + @Override + public void evaluate() throws Throwable { + StatementThread thread= evaluateStatement(); + if (!thread.fFinished) + throwExceptionForUnfinishedThread(thread); + } + + private StatementThread evaluateStatement() throws InterruptedException { + StatementThread thread= new StatementThread(fOriginalStatement); + thread.start(); + thread.join(fTimeout); + thread.interrupt(); + return thread; + } + + private void throwExceptionForUnfinishedThread(StatementThread thread) + throws Throwable { + if (thread.fExceptionThrownByOriginalStatement != null) + throw thread.fExceptionThrownByOriginalStatement; + else + throwTimeoutException(thread); + } + + private void throwTimeoutException(StatementThread thread) throws Exception { + Exception exception= new Exception(String.format( + "test timed out after %d milliseconds", fTimeout)); + exception.setStackTrace(thread.getStackTrace()); + throw exception; + } + + private static class StatementThread extends Thread { + private final Statement fStatement; + + private boolean fFinished= false; + + private Throwable fExceptionThrownByOriginalStatement= null; + + public StatementThread(Statement statement) { + fStatement= statement; + } + + @Override + public void run() { + try { + fStatement.evaluate(); + fFinished= true; + } catch (InterruptedException e) { + //don't log the InterruptedException + } catch (Throwable e) { + fExceptionThrownByOriginalStatement= e; + } + } + } +}
\ No newline at end of file diff --git a/src/org/junit/internal/runners/statements/InvokeMethod.java b/src/org/junit/internal/runners/statements/InvokeMethod.java new file mode 100644 index 0000000..e2e81e1 --- /dev/null +++ b/src/org/junit/internal/runners/statements/InvokeMethod.java @@ -0,0 +1,22 @@ +/** + * + */ +package org.junit.internal.runners.statements; + +import org.junit.runners.model.FrameworkMethod; +import org.junit.runners.model.Statement; + +public class InvokeMethod extends Statement { + private final FrameworkMethod fTestMethod; + private Object fTarget; + + public InvokeMethod(FrameworkMethod testMethod, Object target) { + fTestMethod= testMethod; + fTarget= target; + } + + @Override + public void evaluate() throws Throwable { + fTestMethod.invokeExplosively(fTarget); + } +}
\ No newline at end of file diff --git a/src/org/junit/internal/runners/statements/RunAfters.java b/src/org/junit/internal/runners/statements/RunAfters.java new file mode 100644 index 0000000..475ec72 --- /dev/null +++ b/src/org/junit/internal/runners/statements/RunAfters.java @@ -0,0 +1,43 @@ +/** + * + */ +package org.junit.internal.runners.statements; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.runners.model.FrameworkMethod; +import org.junit.runners.model.MultipleFailureException; +import org.junit.runners.model.Statement; + +public class RunAfters extends Statement { + private final Statement fNext; + + private final Object fTarget; + + private final List<FrameworkMethod> fAfters; + + public RunAfters(Statement next, List<FrameworkMethod> afters, Object target) { + fNext= next; + fAfters= afters; + fTarget= target; + } + + @Override + public void evaluate() throws Throwable { + List<Throwable> errors = new ArrayList<Throwable>(); + try { + fNext.evaluate(); + } catch (Throwable e) { + errors.add(e); + } finally { + for (FrameworkMethod each : fAfters) + try { + each.invokeExplosively(fTarget); + } catch (Throwable e) { + errors.add(e); + } + } + MultipleFailureException.assertEmpty(errors); + } +}
\ No newline at end of file diff --git a/src/org/junit/internal/runners/statements/RunBefores.java b/src/org/junit/internal/runners/statements/RunBefores.java new file mode 100644 index 0000000..66a34e1 --- /dev/null +++ b/src/org/junit/internal/runners/statements/RunBefores.java @@ -0,0 +1,30 @@ +/** + * + */ +package org.junit.internal.runners.statements; + +import java.util.List; + +import org.junit.runners.model.FrameworkMethod; +import org.junit.runners.model.Statement; + +public class RunBefores extends Statement { + private final Statement fNext; + + private final Object fTarget; + + private final List<FrameworkMethod> fBefores; + + public RunBefores(Statement next, List<FrameworkMethod> befores, Object target) { + fNext= next; + fBefores= befores; + fTarget= target; + } + + @Override + public void evaluate() throws Throwable { + for (FrameworkMethod before : fBefores) + before.invokeExplosively(fTarget); + fNext.evaluate(); + } +}
\ No newline at end of file diff --git a/src/org/junit/matchers/JUnitMatchers.java b/src/org/junit/matchers/JUnitMatchers.java new file mode 100644 index 0000000..ec2ec4a --- /dev/null +++ b/src/org/junit/matchers/JUnitMatchers.java @@ -0,0 +1,83 @@ +package org.junit.matchers; + +import org.hamcrest.Matcher; +import org.junit.internal.matchers.CombinableMatcher; +import org.junit.internal.matchers.Each; +import org.junit.internal.matchers.IsCollectionContaining; +import org.junit.internal.matchers.StringContains; + +/** + * Convenience import class: these are useful matchers for use with the assertThat method, but they are + * not currently included in the basic CoreMatchers class from hamcrest. + */ +public class JUnitMatchers { + /** + * @param element + * @return A matcher matching any collection containing element + */ + public static <T> org.hamcrest.Matcher<java.lang.Iterable<T>> hasItem(T element) { + return IsCollectionContaining.hasItem(element); + } + + /** + * @param elementMatcher + * @return A matcher matching any collection containing an element matching elementMatcher + */ + public static <T> org.hamcrest.Matcher<java.lang.Iterable<T>> hasItem(org.hamcrest.Matcher<? extends T> elementMatcher) { + return IsCollectionContaining.hasItem(elementMatcher); + } + + /** + * @param elements + * @return A matcher matching any collection containing every element in elements + */ + public static <T> org.hamcrest.Matcher<java.lang.Iterable<T>> hasItems(T... elements) { + return IsCollectionContaining.hasItems(elements); + } + + /** + * @param elementMatchers + * @return A matcher matching any collection containing at least one element that matches + * each matcher in elementMatcher (this may be one element matching all matchers, + * or different elements matching each matcher) + */ + public static <T> org.hamcrest.Matcher<java.lang.Iterable<T>> hasItems(org.hamcrest.Matcher<? extends T>... elementMatchers) { + return IsCollectionContaining.hasItems(elementMatchers); + } + + /** + * @param elementMatcher + * @return A matcher matching any collection in which every element matches elementMatcher + */ + public static <T> Matcher<Iterable<T>> everyItem(final Matcher<T> elementMatcher) { + return Each.each(elementMatcher); + } + + /** + * @param substring + * @return a matcher matching any string that contains substring + */ + public static org.hamcrest.Matcher<java.lang.String> containsString(java.lang.String substring) { + return StringContains.containsString(substring); + } + + /** + * This is useful for fluently combining matchers that must both pass. For example: + * <pre> + * assertThat(string, both(containsString("a")).and(containsString("b"))); + * </pre> + */ + public static <T> CombinableMatcher<T> both(Matcher<T> matcher) { + return new CombinableMatcher<T>(matcher); + } + + /** + * This is useful for fluently combining matchers where either may pass, for example: + * <pre> + * assertThat(string, either(containsString("a")).or(containsString("b"))); + * </pre> + */ + public static <T> CombinableMatcher<T> either(Matcher<T> matcher) { + return new CombinableMatcher<T>(matcher); + } +} diff --git a/src/org/junit/matchers/package-info.java b/src/org/junit/matchers/package-info.java new file mode 100644 index 0000000..71aca34 --- /dev/null +++ b/src/org/junit/matchers/package-info.java @@ -0,0 +1,9 @@ +/** + * Provides useful additional {@link org.hamcrest.Matcher}s for use with + * the {@link org.junit.Assert#assertThat(Object, org.hamcrest.Matcher)} + * statement + * + * @since 4.0 + * @see org.junit.matchers.JUnitMatchers + */ +package org.junit.matchers;
\ No newline at end of file diff --git a/src/org/junit/package-info.java b/src/org/junit/package-info.java new file mode 100644 index 0000000..bb60d0d --- /dev/null +++ b/src/org/junit/package-info.java @@ -0,0 +1,8 @@ +/** + * Provides JUnit core classes and annotations. + * + * Corresponds to junit.framework in Junit 3.x. + * + * @since 4.0 + */ +package org.junit;
\ No newline at end of file diff --git a/src/org/junit/rules/ErrorCollector.java b/src/org/junit/rules/ErrorCollector.java new file mode 100644 index 0000000..3522a65 --- /dev/null +++ b/src/org/junit/rules/ErrorCollector.java @@ -0,0 +1,85 @@ +/** + * + */ +package org.junit.rules; + +import static org.junit.Assert.assertThat; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.Callable; + +import org.hamcrest.Matcher; +import org.junit.runners.model.MultipleFailureException; + +/** + * The ErrorCollector rule allows execution of a test to continue after the + * first problem is found (for example, to collect _all_ the incorrect rows in a + * table, and report them all at once): + * + * <pre> + * public static class UsesErrorCollectorTwice { + * @Rule + * public ErrorCollector collector= new ErrorCollector(); + * + * @Test + * public void example() { + * collector.addError(new Throwable("first thing went wrong")); + * collector.addError(new Throwable("second thing went wrong")); + * collector.checkThat(getResult(), not(containsString("ERROR!"))); + * // all lines will run, and then a combined failure logged at the end. + * } + * } + * </pre> + */ +public class ErrorCollector extends Verifier { + private List<Throwable> errors= new ArrayList<Throwable>(); + + @Override + protected void verify() throws Throwable { + MultipleFailureException.assertEmpty(errors); + } + + /** + * Adds a Throwable to the table. Execution continues, but the test will fail at the end. + */ + public void addError(Throwable error) { + errors.add(error); + } + + /** + * Adds a failure to the table if {@code matcher} does not match {@code value}. + * Execution continues, but the test will fail at the end if the match fails. + */ + public <T> void checkThat(final T value, final Matcher<T> matcher) { + checkThat("", value, matcher); + } + + /** + * Adds a failure with the given {@code reason} + * to the table if {@code matcher} does not match {@code value}. + * Execution continues, but the test will fail at the end if the match fails. + */ + public <T> void checkThat(final String reason, final T value, final Matcher<T> matcher) { + checkSucceeds(new Callable<Object>() { + public Object call() throws Exception { + assertThat(reason, value, matcher); + return value; + } + }); + } + + /** + * Adds to the table the exception, if any, thrown from {@code callable}. + * Execution continues, but the test will fail at the end if + * {@code callable} threw an exception. + */ + public Object checkSucceeds(Callable<Object> callable) { + try { + return callable.call(); + } catch (Throwable e) { + addError(e); + return null; + } + } +}
\ No newline at end of file diff --git a/src/org/junit/rules/ExpectedException.java b/src/org/junit/rules/ExpectedException.java new file mode 100644 index 0000000..bac2fba --- /dev/null +++ b/src/org/junit/rules/ExpectedException.java @@ -0,0 +1,136 @@ +package org.junit.rules; + +import static org.hamcrest.CoreMatchers.instanceOf; +import static org.junit.matchers.JUnitMatchers.both; +import static org.junit.matchers.JUnitMatchers.containsString; +import org.hamcrest.Description; +import org.hamcrest.Matcher; +import org.hamcrest.StringDescription; +import org.junit.Assert; +import org.junit.internal.matchers.TypeSafeMatcher; +import org.junit.runners.model.Statement; + +/** + * The ExpectedException Rule allows in-test specification of expected exception + * types and messages: + * + * <pre> + * // These tests all pass. + * public static class HasExpectedException { + * @Rule + * public ExpectedException thrown= ExpectedException.none(); + * + * @Test + * public void throwsNothing() { + * // no exception expected, none thrown: passes. + * } + * + * @Test + * public void throwsNullPointerException() { + * thrown.expect(NullPointerException.class); + * throw new NullPointerException(); + * } + * + * @Test + * public void throwsNullPointerExceptionWithMessage() { + * thrown.expect(NullPointerException.class); + * thrown.expectMessage("happened?"); + * thrown.expectMessage(startsWith("What")); + * throw new NullPointerException("What happened?"); + * } + * } + * </pre> + */ +public class ExpectedException implements TestRule { + /** + * @return a Rule that expects no exception to be thrown + * (identical to behavior without this Rule) + */ + public static ExpectedException none() { + return new ExpectedException(); + } + + private Matcher<Object> fMatcher= null; + + private ExpectedException() { + + } + + public Statement apply(Statement base, + org.junit.runner.Description description) { + return new ExpectedExceptionStatement(base); + } + + /** + * Adds {@code matcher} to the list of requirements for any thrown exception. + */ + // Should be able to remove this suppression in some brave new hamcrest world. + @SuppressWarnings("unchecked") + public void expect(Matcher<?> matcher) { + if (fMatcher == null) + fMatcher= (Matcher<Object>) matcher; + else + fMatcher= both(fMatcher).and(matcher); + } + + /** + * Adds to the list of requirements for any thrown exception that it + * should be an instance of {@code type} + */ + public void expect(Class<? extends Throwable> type) { + expect(instanceOf(type)); + } + + /** + * Adds to the list of requirements for any thrown exception that it + * should <em>contain</em> string {@code substring} + */ + public void expectMessage(String substring) { + expectMessage(containsString(substring)); + } + + /** + * Adds {@code matcher} to the list of requirements for the message + * returned from any thrown exception. + */ + public void expectMessage(Matcher<String> matcher) { + expect(hasMessage(matcher)); + } + + private class ExpectedExceptionStatement extends Statement { + private final Statement fNext; + + public ExpectedExceptionStatement(Statement base) { + fNext= base; + } + + @Override + public void evaluate() throws Throwable { + try { + fNext.evaluate(); + } catch (Throwable e) { + if (fMatcher == null) + throw e; + Assert.assertThat(e, fMatcher); + return; + } + if (fMatcher != null) + throw new AssertionError("Expected test to throw " + + StringDescription.toString(fMatcher)); + } + } + + private Matcher<Throwable> hasMessage(final Matcher<String> matcher) { + return new TypeSafeMatcher<Throwable>() { + public void describeTo(Description description) { + description.appendText("exception with message "); + description.appendDescriptionOf(matcher); + } + + @Override + public boolean matchesSafely(Throwable item) { + return matcher.matches(item.getMessage()); + } + }; + } +} diff --git a/src/org/junit/rules/ExternalResource.java b/src/org/junit/rules/ExternalResource.java new file mode 100644 index 0000000..1fe3719 --- /dev/null +++ b/src/org/junit/rules/ExternalResource.java @@ -0,0 +1,68 @@ +package org.junit.rules; + +import org.junit.runner.Description; +import org.junit.runners.model.Statement; + +/** + * A base class for Rules (like TemporaryFolder) that set up an external + * resource before a test (a file, socket, server, database connection, etc.), + * and guarantee to tear it down afterward: + * + * <pre> + * public static class UsesExternalResource { + * Server myServer= new Server(); + * + * @Rule + * public ExternalResource resource= new ExternalResource() { + * @Override + * protected void before() throws Throwable { + * myServer.connect(); + * }; + * + * @Override + * protected void after() { + * myServer.disconnect(); + * }; + * }; + * + * @Test + * public void testFoo() { + * new Client().run(myServer); + * } + * } + * </pre> + */ +public abstract class ExternalResource implements TestRule { + public Statement apply(Statement base, Description description) { + return statement(base); + } + + private Statement statement(final Statement base) { + return new Statement() { + @Override + public void evaluate() throws Throwable { + before(); + try { + base.evaluate(); + } finally { + after(); + } + } + }; + } + + /** + * Override to set up your specific external resource. + * @throws if setup fails (which will disable {@code after} + */ + protected void before() throws Throwable { + // do nothing + } + + /** + * Override to tear down your specific external resource. + */ + protected void after() { + // do nothing + } +} diff --git a/src/org/junit/rules/MethodRule.java b/src/org/junit/rules/MethodRule.java new file mode 100644 index 0000000..5167672 --- /dev/null +++ b/src/org/junit/rules/MethodRule.java @@ -0,0 +1,40 @@ +package org.junit.rules; + +import org.junit.Rule; +import org.junit.runners.model.FrameworkMethod; +import org.junit.runners.model.Statement; + +/** + * A MethodRule is an alteration in how a test method is run and reported. + * Multiple {@link MethodRule}s can be applied to a test method. The + * {@link Statement} that executes the method is passed to each annotated + * {@link Rule} in turn, and each may return a substitute or modified + * {@link Statement}, which is passed to the next {@link Rule}, if any. For + * examples of how this can be useful, see these provided MethodRules, + * or write your own: + * + * <ul> + * <li>{@link ErrorCollector}: collect multiple errors in one test method</li> + * <li>{@link ExpectedException}: make flexible assertions about thrown exceptions</li> + * <li>{@link ExternalResource}: start and stop a server, for example</li> + * <li>{@link TemporaryFolder}: create fresh files, and delete after test</li> + * <li>{@link TestName}: remember the test name for use during the method</li> + * <li>{@link TestWatchman}: add logic at events during method execution</li> + * <li>{@link Timeout}: cause test to fail after a set time</li> + * <li>{@link Verifier}: fail test if object state ends up incorrect</li> + * </ul> + */ +@Deprecated +public interface MethodRule { + /** + * Modifies the method-running {@link Statement} to implement an additional + * test-running rule. + * + * @param base The {@link Statement} to be modified + * @param method The method to be run + * @param target The object on with the method will be run. + * @return a new statement, which may be the same as {@code base}, + * a wrapper around {@code base}, or a completely new Statement. + */ + Statement apply(Statement base, FrameworkMethod method, Object target); +}
\ No newline at end of file diff --git a/src/org/junit/rules/RuleChain.java b/src/org/junit/rules/RuleChain.java new file mode 100644 index 0000000..8af3c05 --- /dev/null +++ b/src/org/junit/rules/RuleChain.java @@ -0,0 +1,99 @@ +/** + * + */ +package org.junit.rules; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.junit.runner.Description; +import org.junit.runners.model.Statement; + +/** + * The RuleChain rule allows ordering of TestRules. You create a + * {@code RuleChain} with {@link #outerRule(TestRule)} and subsequent calls of + * {@link #around(TestRule)}: + * + * <pre> + * public static class UseRuleChain { + * @Rule + * public TestRule chain= RuleChain + * .outerRule(new LoggingRule("outer rule") + * .around(new LoggingRule("middle rule") + * .around(new LoggingRule("inner rule"); + * + * @Test + * public void example() { + * assertTrue(true); + * } + * } + * </pre> + * + * writes the log + * + * <pre> + * starting outer rule + * starting middle rule + * starting inner rule + * finished inner rule + * finished middle rule + * finished outer rule + * </pre> + */ +public class RuleChain implements TestRule { + private static final RuleChain EMPTY_CHAIN= new RuleChain( + Collections.<TestRule> emptyList()); + + private List<TestRule> rulesStartingWithInnerMost; + + /** + * Returns a {@code RuleChain} without a {@link TestRule}. This method may + * be the starting point of a {@code RuleChain}. + * + * @return a {@code RuleChain} without a {@link TestRule}. + */ + public static RuleChain emptyRuleChain() { + return EMPTY_CHAIN; + } + + /** + * Returns a {@code RuleChain} with a single {@link TestRule}. This method + * is the usual starting point of a {@code RuleChain}. + * + * @param outerRule + * the outer rule of the {@code RuleChain}. + * @return a {@code RuleChain} with a single {@link TestRule}. + */ + public static RuleChain outerRule(TestRule outerRule) { + return emptyRuleChain().around(outerRule); + } + + private RuleChain(List<TestRule> rules) { + this.rulesStartingWithInnerMost= rules; + } + + /** + * Create a new {@code RuleChain}, which encloses the {@code nextRule} with + * the rules of the current {@code RuleChain}. + * + * @param enclosedRule + * the rule to enclose. + * @return a new {@code RuleChain}. + */ + public RuleChain around(TestRule enclosedRule) { + List<TestRule> rulesOfNewChain= new ArrayList<TestRule>(); + rulesOfNewChain.add(enclosedRule); + rulesOfNewChain.addAll(rulesStartingWithInnerMost); + return new RuleChain(rulesOfNewChain); + } + + /** + * {@inheritDoc} + */ + public Statement apply(Statement base, Description description) { + for (TestRule each : rulesStartingWithInnerMost) + base= each.apply(base, description); + return base; + } +}
\ No newline at end of file diff --git a/src/org/junit/rules/RunRules.java b/src/org/junit/rules/RunRules.java new file mode 100644 index 0000000..d5905b9 --- /dev/null +++ b/src/org/junit/rules/RunRules.java @@ -0,0 +1,27 @@ +package org.junit.rules; + +import org.junit.runner.Description; +import org.junit.runners.model.Statement; + +/** + * Runs a collection of rules on a statement. + */ +public class RunRules extends Statement { + private final Statement statement; + + public RunRules(Statement base, Iterable<TestRule> rules, Description description) { + statement= applyAll(base, rules, description); + } + + @Override + public void evaluate() throws Throwable { + statement.evaluate(); + } + + private static Statement applyAll(Statement result, Iterable<TestRule> rules, + Description description) { + for (TestRule each : rules) + result= each.apply(result, description); + return result; + } +} diff --git a/src/org/junit/rules/TemporaryFolder.java b/src/org/junit/rules/TemporaryFolder.java new file mode 100644 index 0000000..a7c82aa --- /dev/null +++ b/src/org/junit/rules/TemporaryFolder.java @@ -0,0 +1,113 @@ +package org.junit.rules; + +import java.io.File; +import java.io.IOException; + +import org.junit.Rule; + +/** + * The TemporaryFolder Rule allows creation of files and folders that are + * guaranteed to be deleted when the test method finishes (whether it passes or + * fails): + * + * <pre> + * public static class HasTempFolder { + * @Rule + * public TemporaryFolder folder= new TemporaryFolder(); + * + * @Test + * public void testUsingTempFolder() throws IOException { + * File createdFile= folder.newFile("myfile.txt"); + * File createdFolder= folder.newFolder("subfolder"); + * // ... + * } + * } + * </pre> + */ +public class TemporaryFolder extends ExternalResource { + private File folder; + + @Override + protected void before() throws Throwable { + create(); + } + + @Override + protected void after() { + delete(); + } + + // testing purposes only + /** + * for testing purposes only. Do not use. + */ + public void create() throws IOException { + folder= newFolder(); + } + + /** + * Returns a new fresh file with the given name under the temporary folder. + */ + public File newFile(String fileName) throws IOException { + File file= new File(getRoot(), fileName); + file.createNewFile(); + return file; + } + + /** + * Returns a new fresh file with a random name under the temporary folder. + */ + public File newFile() throws IOException { + return File.createTempFile("junit", null, folder); + } + + /** + * Returns a new fresh folder with the given name under the temporary folder. + */ + public File newFolder(String... folderNames) { + File file = getRoot(); + for (String folderName : folderNames) { + file = new File(file, folderName); + file.mkdir(); + } + return file; + } + + /** + * Returns a new fresh folder with a random name under the temporary + * folder. + */ + public File newFolder() throws IOException { + File createdFolder= File.createTempFile("junit", "", folder); + createdFolder.delete(); + createdFolder.mkdir(); + return createdFolder; + } + + /** + * @return the location of this temporary folder. + */ + public File getRoot() { + if (folder == null) { + throw new IllegalStateException("the temporary folder has not yet been created"); + } + return folder; + } + + /** + * Delete all files and folders under the temporary folder. + * Usually not called directly, since it is automatically applied + * by the {@link Rule} + */ + public void delete() { + recursiveDelete(folder); + } + + private void recursiveDelete(File file) { + File[] files= file.listFiles(); + if (files != null) + for (File each : files) + recursiveDelete(each); + file.delete(); + } +} diff --git a/src/org/junit/rules/TestName.java b/src/org/junit/rules/TestName.java new file mode 100644 index 0000000..c4ab9ce --- /dev/null +++ b/src/org/junit/rules/TestName.java @@ -0,0 +1,39 @@ +package org.junit.rules; + +import org.junit.runner.Description; + +/** + * The TestName Rule makes the current test name available inside test methods: + * + * <pre> + * public class TestNameTest { + * @Rule + * public TestName name= new TestName(); + * + * @Test + * public void testA() { + * assertEquals("testA", name.getMethodName()); + * } + * + * @Test + * public void testB() { + * assertEquals("testB", name.getMethodName()); + * } + * } + * </pre> + */ +public class TestName extends TestWatcher { + private String fName; + + @Override + protected void starting(Description d) { + fName= d.getMethodName(); + } + + /** + * @return the name of the currently-running test method + */ + public String getMethodName() { + return fName; + } +} diff --git a/src/org/junit/rules/TestRule.java b/src/org/junit/rules/TestRule.java new file mode 100644 index 0000000..b7760c4 --- /dev/null +++ b/src/org/junit/rules/TestRule.java @@ -0,0 +1,54 @@ +package org.junit.rules; + +import org.junit.runner.Description; +import org.junit.runners.model.Statement; + +/** + * A TestRule is an alteration in how a test method, or set of test methods, + * is run and reported. A {@link TestRule} may add additional checks that cause + * a test that would otherwise fail to pass, or it may perform necessary setup or + * cleanup for tests, or it may observe test execution to report it elsewhere. + * {@link TestRule}s can do everything that could be done previously with + * methods annotated with {@link org.junit.Before}, + * {@link org.junit.After}, {@link org.junit.BeforeClass}, or + * {@link org.junit.AfterClass}, but they are more powerful, and more easily + * shared + * between projects and classes. + * + * The default JUnit test runners for suites and + * individual test cases recognize {@link TestRule}s introduced in two different + * ways. {@link org.junit.Rule} annotates method-level + * {@link TestRule}s, and {@link org.junit.ClassRule} + * annotates class-level {@link TestRule}s. See Javadoc for those annotations + * for more information. + * + * Multiple {@link TestRule}s can be applied to a test or suite execution. The + * {@link Statement} that executes the method or suite is passed to each annotated + * {@link org.junit.Rule} in turn, and each may return a substitute or modified + * {@link Statement}, which is passed to the next {@link org.junit.Rule}, if any. For + * examples of how this can be useful, see these provided TestRules, + * or write your own: + * + * <ul> + * <li>{@link ErrorCollector}: collect multiple errors in one test method</li> + * <li>{@link ExpectedException}: make flexible assertions about thrown exceptions</li> + * <li>{@link ExternalResource}: start and stop a server, for example</li> + * <li>{@link TemporaryFolder}: create fresh files, and delete after test</li> + * <li>{@link TestName}: remember the test name for use during the method</li> + * <li>{@link TestWatcher}: add logic at events during method execution</li> + * <li>{@link Timeout}: cause test to fail after a set time</li> + * <li>{@link Verifier}: fail test if object state ends up incorrect</li> + * </ul> + */ +public interface TestRule { + /** + * Modifies the method-running {@link Statement} to implement this + * test-running rule. + * + * @param base The {@link Statement} to be modified + * @param description A {@link Description} of the test implemented in {@code base} + * @return a new statement, which may be the same as {@code base}, + * a wrapper around {@code base}, or a completely new Statement. + */ + Statement apply(Statement base, Description description); +} diff --git a/src/org/junit/rules/TestWatcher.java b/src/org/junit/rules/TestWatcher.java new file mode 100644 index 0000000..351b449 --- /dev/null +++ b/src/org/junit/rules/TestWatcher.java @@ -0,0 +1,94 @@ +package org.junit.rules; + +import org.junit.internal.AssumptionViolatedException; +import org.junit.runner.Description; +import org.junit.runners.model.Statement; + +/** + * TestWatcher is a base class for Rules that take note of the testing + * action, without modifying it. For example, this class will keep a log of each + * passing and failing test: + * + * <pre> + * public static class WatchmanTest { + * private static String watchedLog; + * + * @Rule + * public MethodRule watchman= new TestWatcher() { + * @Override + * protected void failed(Description d) { + * watchedLog+= d + "\n"; + * } + * + * @Override + * protected void succeeded(Description d) { + * watchedLog+= d + " " + "success!\n"; + * } + * }; + * + * @Test + * public void fails() { + * fail(); + * } + * + * @Test + * public void succeeds() { + * } + * } + * </pre> + */ +public abstract class TestWatcher implements TestRule { + public Statement apply(final Statement base, final Description description) { + return new Statement() { + @Override + public void evaluate() throws Throwable { + starting(description); + try { + base.evaluate(); + succeeded(description); + } catch (AssumptionViolatedException e) { + throw e; + } catch (Throwable t) { + failed(t, description); + throw t; + } finally { + finished(description); + } + } + }; + } + + /** + * Invoked when a test succeeds + * + * @param description + */ + protected void succeeded(Description description) { + } + + /** + * Invoked when a test fails + * + * @param e + * @param description + */ + protected void failed(Throwable e, Description description) { + } + + /** + * Invoked when a test is about to start + * + * @param description + */ + protected void starting(Description description) { + } + + + /** + * Invoked when a test method finishes (whether passing or failing) + * + * @param description + */ + protected void finished(Description description) { + } +} diff --git a/src/org/junit/rules/TestWatchman.java b/src/org/junit/rules/TestWatchman.java new file mode 100644 index 0000000..15daa64 --- /dev/null +++ b/src/org/junit/rules/TestWatchman.java @@ -0,0 +1,100 @@ +package org.junit.rules; + +import org.junit.internal.AssumptionViolatedException; +import org.junit.runners.model.FrameworkMethod; +import org.junit.runners.model.Statement; + +/** + * TestWatchman is a base class for Rules that take note of the testing + * action, without modifying it. For example, this class will keep a log of each + * passing and failing test: + * + * <pre> + * public static class WatchmanTest { + * private static String watchedLog; + * + * @Rule + * public MethodRule watchman= new TestWatchman() { + * @Override + * public void failed(Throwable e, FrameworkMethod method) { + * watchedLog+= method.getName() + " " + e.getClass().getSimpleName() + * + "\n"; + * } + * + * @Override + * public void succeeded(FrameworkMethod method) { + * watchedLog+= method.getName() + " " + "success!\n"; + * } + * }; + * + * @Test + * public void fails() { + * fail(); + * } + * + * @Test + * public void succeeds() { + * } + * } + * </pre> + * + * @deprecated {@link MethodRule} is deprecated. + * Use {@link TestWatcher} implements {@link TestRule} instead. + */ +@Deprecated +public class TestWatchman implements MethodRule { + public Statement apply(final Statement base, final FrameworkMethod method, + Object target) { + return new Statement() { + @Override + public void evaluate() throws Throwable { + starting(method); + try { + base.evaluate(); + succeeded(method); + } catch (AssumptionViolatedException e) { + throw e; + } catch (Throwable t) { + failed(t, method); + throw t; + } finally { + finished(method); + } + } + }; + } + + /** + * Invoked when a test method succeeds + * + * @param method + */ + public void succeeded(FrameworkMethod method) { + } + + /** + * Invoked when a test method fails + * + * @param e + * @param method + */ + public void failed(Throwable e, FrameworkMethod method) { + } + + /** + * Invoked when a test method is about to start + * + * @param method + */ + public void starting(FrameworkMethod method) { + } + + + /** + * Invoked when a test method finishes (whether passing or failing) + * + * @param method + */ + public void finished(FrameworkMethod method) { + } +} diff --git a/src/org/junit/rules/Timeout.java b/src/org/junit/rules/Timeout.java new file mode 100644 index 0000000..85ce6d6 --- /dev/null +++ b/src/org/junit/rules/Timeout.java @@ -0,0 +1,49 @@ +/** + * + */ +package org.junit.rules; + +import org.junit.internal.runners.statements.FailOnTimeout; +import org.junit.runner.Description; +import org.junit.runners.model.Statement; + +/** + * The Timeout Rule applies the same timeout to all test methods in a class: + * + * <pre> + * public static class HasGlobalTimeout { + * public static String log; + * + * @Rule + * public MethodRule globalTimeout= new Timeout(20); + * + * @Test + * public void testInfiniteLoop1() { + * log+= "ran1"; + * for (;;) { + * } + * } + * + * @Test + * public void testInfiniteLoop2() { + * log+= "ran2"; + * for (;;) { + * } + * } + * } + * </pre> + */ +public class Timeout implements TestRule { + private final int fMillis; + + /** + * @param millis the millisecond timeout + */ + public Timeout(int millis) { + fMillis= millis; + } + + public Statement apply(Statement base, Description description) { + return new FailOnTimeout(base, fMillis); + } +}
\ No newline at end of file diff --git a/src/org/junit/rules/Verifier.java b/src/org/junit/rules/Verifier.java new file mode 100644 index 0000000..be1a55e --- /dev/null +++ b/src/org/junit/rules/Verifier.java @@ -0,0 +1,45 @@ +package org.junit.rules; + +import org.junit.runner.Description; +import org.junit.runners.model.Statement; + +/** + * Verifier is a base class for Rules like ErrorCollector, which can turn + * otherwise passing test methods into failing tests if a verification check is + * failed + * + * <pre> + * public static class ErrorLogVerifier() { + * private ErrorLog errorLog = new ErrorLog(); + * + * @Rule + * public MethodRule verifier = new Verifier() { + * @Override public void verify() { + * assertTrue(errorLog.isEmpty()); + * } + * } + * + * @Test public void testThatMightWriteErrorLog() { + * // ... + * } + * } + * </pre> + */ +public class Verifier implements TestRule { + public Statement apply(final Statement base, Description description) { + return new Statement() { + @Override + public void evaluate() throws Throwable { + base.evaluate(); + verify(); + } + }; + } + + /** + * Override this to add verification logic. Overrides should throw an + * exception to indicate that verification failed. + */ + protected void verify() throws Throwable { + } +} diff --git a/src/org/junit/runner/Computer.java b/src/org/junit/runner/Computer.java new file mode 100644 index 0000000..939f702 --- /dev/null +++ b/src/org/junit/runner/Computer.java @@ -0,0 +1,40 @@ +package org.junit.runner; + +import org.junit.runners.Suite; +import org.junit.runners.model.InitializationError; +import org.junit.runners.model.RunnerBuilder; + +/** + * Represents a strategy for computing runners and suites. + * WARNING: this class is very likely to undergo serious changes in version 4.8 and + * beyond. + */ +public class Computer { + /** + * Returns a new default computer, which runs tests in serial order + */ + public static Computer serial() { + return new Computer(); + } + + /** + * Create a suite for {@code classes}, building Runners with {@code builder}. + * Throws an InitializationError if Runner construction fails + */ + public Runner getSuite(final RunnerBuilder builder, + Class<?>[] classes) throws InitializationError { + return new Suite(new RunnerBuilder() { + @Override + public Runner runnerForClass(Class<?> testClass) throws Throwable { + return getRunner(builder, testClass); + } + }, classes); + } + + /** + * Create a single-class runner for {@code testClass}, using {@code builder} + */ + protected Runner getRunner(RunnerBuilder builder, Class<?> testClass) throws Throwable { + return builder.runnerForClass(testClass); + } +} diff --git a/src/org/junit/runner/Describable.java b/src/org/junit/runner/Describable.java new file mode 100644 index 0000000..e59cc01 --- /dev/null +++ b/src/org/junit/runner/Describable.java @@ -0,0 +1,12 @@ +package org.junit.runner; + + +/** + * Represents an object that can describe itself + */ +public interface Describable { + /** + * @return a {@link Description} showing the tests to be run by the receiver + */ + public abstract Description getDescription(); +}
\ No newline at end of file diff --git a/src/org/junit/runner/Description.java b/src/org/junit/runner/Description.java new file mode 100644 index 0000000..b3083d5 --- /dev/null +++ b/src/org/junit/runner/Description.java @@ -0,0 +1,242 @@ +package org.junit.runner; + +import java.io.Serializable; +import java.lang.annotation.Annotation; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * <p>A <code>Description</code> describes a test which is to be run or has been run. <code>Descriptions</code> + * can be atomic (a single test) or compound (containing children tests). <code>Descriptions</code> are used + * to provide feedback about the tests that are about to run (for example, the tree view + * visible in many IDEs) or tests that have been run (for example, the failures view).</p> + * + * <p><code>Descriptions</code> are implemented as a single class rather than a Composite because + * they are entirely informational. They contain no logic aside from counting their tests.</p> + * + * <p>In the past, we used the raw {@link junit.framework.TestCase}s and {@link junit.framework.TestSuite}s + * to display the tree of tests. This was no longer viable in JUnit 4 because atomic tests no longer have + * a superclass below {@link Object}. We needed a way to pass a class and name together. Description + * emerged from this.</p> + * + * @see org.junit.runner.Request + * @see org.junit.runner.Runner + */ +public class Description implements Serializable { + private static final long serialVersionUID = 1L; + + /** + * Create a <code>Description</code> named <code>name</code>. + * Generally, you will add children to this <code>Description</code>. + * @param name the name of the <code>Description</code> + * @param annotations + * @return a <code>Description</code> named <code>name</code> + */ + public static Description createSuiteDescription(String name, Annotation... annotations) { + if (name.length() == 0) + throw new IllegalArgumentException("name must have non-zero length"); + return new Description(name, annotations); + } + + /** + * Create a <code>Description</code> of a single test named <code>name</code> in the class <code>clazz</code>. + * Generally, this will be a leaf <code>Description</code>. + * @param clazz the class of the test + * @param name the name of the test (a method name for test annotated with {@link org.junit.Test}) + * @param annotations meta-data about the test, for downstream interpreters + * @return a <code>Description</code> named <code>name</code> + */ + public static Description createTestDescription(Class<?> clazz, String name, Annotation... annotations) { + return new Description(String.format("%s(%s)", name, clazz.getName()), annotations); + } + + /** + * Create a <code>Description</code> of a single test named <code>name</code> in the class <code>clazz</code>. + * Generally, this will be a leaf <code>Description</code>. + * (This remains for binary compatibility with clients of JUnit 4.3) + * @param clazz the class of the test + * @param name the name of the test (a method name for test annotated with {@link org.junit.Test}) + * @return a <code>Description</code> named <code>name</code> + */ + public static Description createTestDescription(Class<?> clazz, String name) { + return createTestDescription(clazz, name, new Annotation[0]); + } + + /** + * Create a <code>Description</code> named after <code>testClass</code> + * @param testClass A {@link Class} containing tests + * @return a <code>Description</code> of <code>testClass</code> + */ + public static Description createSuiteDescription(Class<?> testClass) { + return new Description(testClass.getName(), testClass.getAnnotations()); + } + + /** + * Describes a Runner which runs no tests + */ + public static final Description EMPTY= new Description("No Tests"); + + /** + * Describes a step in the test-running mechanism that goes so wrong no + * other description can be used (for example, an exception thrown from a Runner's + * constructor + */ + public static final Description TEST_MECHANISM= new Description("Test mechanism"); + + private final ArrayList<Description> fChildren= new ArrayList<Description>(); + private final String fDisplayName; + + private final Annotation[] fAnnotations; + + private Description(final String displayName, Annotation... annotations) { + fDisplayName= displayName; + fAnnotations= annotations; + } + + /** + * @return a user-understandable label + */ + public String getDisplayName() { + return fDisplayName; + } + + /** + * Add <code>Description</code> as a child of the receiver. + * @param description the soon-to-be child. + */ + public void addChild(Description description) { + getChildren().add(description); + } + + /** + * @return the receiver's children, if any + */ + public ArrayList<Description> getChildren() { + return fChildren; + } + + /** + * @return <code>true</code> if the receiver is a suite + */ + public boolean isSuite() { + return !isTest(); + } + + /** + * @return <code>true</code> if the receiver is an atomic test + */ + public boolean isTest() { + return getChildren().isEmpty(); + } + + /** + * @return the total number of atomic tests in the receiver + */ + public int testCount() { + if (isTest()) + return 1; + int result= 0; + for (Description child : getChildren()) + result+= child.testCount(); + return result; + } + + @Override + public int hashCode() { + return getDisplayName().hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof Description)) + return false; + Description d = (Description) obj; + return getDisplayName().equals(d.getDisplayName()); + } + + @Override + public String toString() { + return getDisplayName(); + } + + /** + * @return true if this is a description of a Runner that runs no tests + */ + public boolean isEmpty() { + return equals(EMPTY); + } + + /** + * @return a copy of this description, with no children (on the assumption that some of the + * children will be added back) + */ + public Description childlessCopy() { + return new Description(fDisplayName, fAnnotations); + } + + /** + * @return the annotation of type annotationType that is attached to this description node, + * or null if none exists + */ + public <T extends Annotation> T getAnnotation(Class<T> annotationType) { + for (Annotation each : fAnnotations) + if (each.annotationType().equals(annotationType)) + return annotationType.cast(each); + return null; + } + + /** + * @return all of the annotations attached to this description node + */ + public Collection<Annotation> getAnnotations() { + return Arrays.asList(fAnnotations); + } + + /** + * @return If this describes a method invocation, + * the class of the test instance. + */ + public Class<?> getTestClass() { + String name= getClassName(); + if (name == null) + return null; + try { + return Class.forName(name); + } catch (ClassNotFoundException e) { + return null; + } + } + + /** + * @return If this describes a method invocation, + * the name of the class of the test instance + */ + public String getClassName() { + Matcher matcher= methodStringMatcher(); + return matcher.matches() + ? matcher.group(2) + : toString(); + } + + /** + * @return If this describes a method invocation, + * the name of the method (or null if not) + */ + public String getMethodName() { + return parseMethod(); + } + + private String parseMethod() { + Matcher matcher= methodStringMatcher(); + if (matcher.matches()) + return matcher.group(1); + return null; + } + + private Matcher methodStringMatcher() { + return Pattern.compile("(.*)\\((.*)\\)").matcher(toString()); + } +}
\ No newline at end of file diff --git a/src/org/junit/runner/JUnitCore.java b/src/org/junit/runner/JUnitCore.java new file mode 100644 index 0000000..2fcd3b3 --- /dev/null +++ b/src/org/junit/runner/JUnitCore.java @@ -0,0 +1,186 @@ +package org.junit.runner; + +import java.util.ArrayList; +import java.util.List; + +import junit.runner.Version; +import org.junit.internal.JUnitSystem; +import org.junit.internal.RealSystem; +import org.junit.internal.TextListener; +import org.junit.internal.runners.JUnit38ClassRunner; +import org.junit.runner.notification.Failure; +import org.junit.runner.notification.RunListener; +import org.junit.runner.notification.RunNotifier; + +/** + * <code>JUnitCore</code> is a facade for running tests. It supports running JUnit 4 tests, + * JUnit 3.8.x tests, and mixtures. To run tests from the command line, run + * <code>java org.junit.runner.JUnitCore TestClass1 TestClass2 ...</code>. + * For one-shot test runs, use the static method {@link #runClasses(Class[])}. + * If you want to add special listeners, + * create an instance of {@link org.junit.runner.JUnitCore} first and use it to run the tests. + * + * @see org.junit.runner.Result + * @see org.junit.runner.notification.RunListener + * @see org.junit.runner.Request + */ +public class JUnitCore { + private RunNotifier fNotifier; + + /** + * Create a new <code>JUnitCore</code> to run tests. + */ + public JUnitCore() { + fNotifier= new RunNotifier(); + } + + /** + * Run the tests contained in the classes named in the <code>args</code>. + * If all tests run successfully, exit with a status of 0. Otherwise exit with a status of 1. + * Write feedback while tests are running and write + * stack traces for all failed tests after the tests all complete. + * @param args names of classes in which to find tests to run + */ + public static void main(String... args) { + runMainAndExit(new RealSystem(), args); + } + + /** + * Do not use. Testing purposes only. + * @param system + */ + public static void runMainAndExit(JUnitSystem system, String... args) { + Result result= new JUnitCore().runMain(system, args); + system.exit(result.wasSuccessful() ? 0 : 1); + } + + /** + * Run the tests contained in <code>classes</code>. Write feedback while the tests + * are running and write stack traces for all failed tests after all tests complete. This is + * similar to {@link #main(String[])}, but intended to be used programmatically. + * @param computer Helps construct Runners from classes + * @param classes Classes in which to find tests + * @return a {@link Result} describing the details of the test run and the failed tests. + */ + public static Result runClasses(Computer computer, Class<?>... classes) { + return new JUnitCore().run(computer, classes); + } + /** + * Run the tests contained in <code>classes</code>. Write feedback while the tests + * are running and write stack traces for all failed tests after all tests complete. This is + * similar to {@link #main(String[])}, but intended to be used programmatically. + * @param classes Classes in which to find tests + * @return a {@link Result} describing the details of the test run and the failed tests. + */ + public static Result runClasses(Class<?>... classes) { + return new JUnitCore().run(defaultComputer(), classes); + } + + /** + * Do not use. Testing purposes only. + * @param system + */ + public Result runMain(JUnitSystem system, String... args) { + system.out().println("JUnit version " + Version.id()); + List<Class<?>> classes= new ArrayList<Class<?>>(); + List<Failure> missingClasses= new ArrayList<Failure>(); + for (String each : args) + try { + classes.add(Class.forName(each)); + } catch (ClassNotFoundException e) { + system.out().println("Could not find class: " + each); + Description description= Description.createSuiteDescription(each); + Failure failure= new Failure(description, e); + missingClasses.add(failure); + } + RunListener listener= new TextListener(system); + addListener(listener); + Result result= run(classes.toArray(new Class[0])); + for (Failure each : missingClasses) + result.getFailures().add(each); + return result; + } + + /** + * @return the version number of this release + */ + public String getVersion() { + return Version.id(); + } + + /** + * Run all the tests in <code>classes</code>. + * @param classes the classes containing tests + * @return a {@link Result} describing the details of the test run and the failed tests. + */ + public Result run(Class<?>... classes) { + return run(Request.classes(defaultComputer(), classes)); + } + + /** + * Run all the tests in <code>classes</code>. + * @param computer Helps construct Runners from classes + * @param classes the classes containing tests + * @return a {@link Result} describing the details of the test run and the failed tests. + */ + public Result run(Computer computer, Class<?>... classes) { + return run(Request.classes(computer, classes)); + } + + /** + * 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.getRunner()); + } + + /** + * Run all the tests contained in JUnit 3.8.x <code>test</code>. Here for backward compatibility. + * @param test the old-style test + * @return a {@link Result} describing the details of the test run and the failed tests. + */ + public Result run(junit.framework.Test test) { + return run(new JUnit38ClassRunner(test)); + } + + /** + * Do not use. Testing purposes only. + */ + public Result run(Runner runner) { + Result result= new Result(); + RunListener listener= result.createListener(); + fNotifier.addFirstListener(listener); + try { + fNotifier.fireTestRunStarted(runner.getDescription()); + runner.run(fNotifier); + fNotifier.fireTestRunFinished(result); + } finally { + removeListener(listener); + } + return result; + } + + /** + * Add a listener to be notified as the tests run. + * @param listener the listener to add + * @see org.junit.runner.notification.RunListener + */ + public void addListener(RunListener listener) { + fNotifier.addListener(listener); + } + + /** + * Remove a listener. + * @param listener the listener to remove + */ + public void removeListener(RunListener listener) { + fNotifier.removeListener(listener); + } + + static Computer defaultComputer() { + return new Computer(); + } + +} diff --git a/src/org/junit/runner/Request.java b/src/org/junit/runner/Request.java new file mode 100644 index 0000000..310b915 --- /dev/null +++ b/src/org/junit/runner/Request.java @@ -0,0 +1,161 @@ +package org.junit.runner; + +import java.util.Comparator; + +import org.junit.internal.builders.AllDefaultPossibilitiesBuilder; +import org.junit.internal.requests.ClassRequest; +import org.junit.internal.requests.FilterRequest; +import org.junit.internal.requests.SortingRequest; +import org.junit.internal.runners.ErrorReportingRunner; +import org.junit.runner.manipulation.Filter; +import org.junit.runners.model.InitializationError; + +/** + * <p>A <code>Request</code> is an abstract description of tests to be run. Older versions of + * JUnit did not need such a concept--tests to be run were described either by classes containing + * tests or a tree of {@link org.junit.Test}s. However, we want to support filtering and sorting, + * so we need a more abstract specification than the tests themselves and a richer + * specification than just the classes.</p> + * + * <p>The flow when JUnit runs tests is that a <code>Request</code> specifies some tests to be run -> + * a {@link org.junit.runner.Runner} is created for each class implied by the <code>Request</code> -> + * the {@link org.junit.runner.Runner} returns a detailed {@link org.junit.runner.Description} + * which is a tree structure of the tests to be run.</p> + */ +public abstract class Request { + /** + * Create a <code>Request</code> that, when processed, will run a single test. + * This is done by filtering out all other tests. This method is used to support rerunning + * single tests. + * @param clazz the class of the test + * @param methodName the name of the test + * @return a <code>Request</code> that will cause a single test be run + */ + public static Request method(Class<?> clazz, String methodName) { + Description method= Description.createTestDescription(clazz, methodName); + return Request.aClass(clazz).filterWith(method); + } + + /** + * Create a <code>Request</code> that, when processed, will run all the tests + * in a class. The odd name is necessary because <code>class</code> is a reserved word. + * @param clazz the class containing the tests + * @return a <code>Request</code> that will cause all tests in the class to be run + */ + public static Request aClass(Class<?> clazz) { + return new ClassRequest(clazz); + } + + /** + * Create a <code>Request</code> that, when processed, will run all the tests + * in a class. If the class has a suite() method, it will be ignored. + * @param clazz the class containing the tests + * @return a <code>Request</code> that will cause all tests in the class to be run + */ + public static Request classWithoutSuiteMethod(Class<?> clazz) { + return new ClassRequest(clazz, false); + } + + /** + * Create a <code>Request</code> that, when processed, will run all the tests + * in a set of classes. + * @param computer Helps construct Runners from classes + * @param classes the classes containing the tests + * @return a <code>Request</code> that will cause all tests in the classes to be run + */ + public static Request classes(Computer computer, Class<?>... classes) { + try { + AllDefaultPossibilitiesBuilder builder= new AllDefaultPossibilitiesBuilder(true); + Runner suite= computer.getSuite(builder, classes); + return runner(suite); + } catch (InitializationError e) { + throw new RuntimeException( + "Bug in saff's brain: Suite constructor, called as above, should always complete"); + } + } + + /** + * Create a <code>Request</code> that, when processed, will run all the tests + * in a set of classes with the default <code>Computer</code>. + * @param classes the classes containing the tests + * @return a <code>Request</code> that will cause all tests in the classes to be run + */ + public static Request classes(Class<?>... classes) { + return classes(JUnitCore.defaultComputer(), classes); + } + + + /** + * Not used within JUnit. Clients should simply instantiate ErrorReportingRunner themselves + */ + @Deprecated + public static Request errorReport(Class<?> klass, Throwable cause) { + return runner(new ErrorReportingRunner(klass, cause)); + } + + /** + * @param runner the runner to return + * @return a <code>Request</code> that will run the given runner when invoked + */ + public static Request runner(final Runner runner) { + return new Request(){ + @Override + public Runner getRunner() { + return runner; + } + }; + } + + /** + * Returns a {@link Runner} for this Request + * @return corresponding {@link Runner} for this Request + */ + public abstract Runner getRunner(); + + /** + * Returns a Request that only contains those tests that should run when + * <code>filter</code> is applied + * @param filter The {@link Filter} to apply to this Request + * @return the filtered Request + */ + public Request filterWith(Filter filter) { + return new FilterRequest(this, filter); + } + + /** + * Returns a Request that only runs contains tests whose {@link Description} + * equals <code>desiredDescription</code> + * @param desiredDescription {@link Description} of those tests that should be run + * @return the filtered Request + */ + public Request filterWith(final Description desiredDescription) { + return filterWith(Filter.matchMethodDescription(desiredDescription)); + } + + /** + * Returns a Request whose Tests can be run in a certain order, defined by + * <code>comparator</code> + * + * For example, here is code to run a test suite in alphabetical order: + * + * <pre> + private static Comparator<Description> forward() { + return new Comparator<Description>() { + public int compare(Description o1, Description o2) { + return o1.getDisplayName().compareTo(o2.getDisplayName()); + } + }; + } + + public static main() { + new JUnitCore().run(Request.aClass(AllTests.class).sortWith(forward())); + } + * </pre> + * + * @param comparator definition of the order of the tests in this Request + * @return a Request with ordered Tests + */ + public Request sortWith(Comparator<Description> comparator) { + return new SortingRequest(this, comparator); + } +} diff --git a/src/org/junit/runner/Result.java b/src/org/junit/runner/Result.java new file mode 100644 index 0000000..edfb97c --- /dev/null +++ b/src/org/junit/runner/Result.java @@ -0,0 +1,106 @@ +package org.junit.runner; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; +import java.util.Collections; +import java.util.concurrent.atomic.AtomicInteger; + +import org.junit.runner.notification.Failure; +import org.junit.runner.notification.RunListener; + +/** + * A <code>Result</code> collects and summarizes information from running multiple + * tests. Since tests are expected to run correctly, successful tests are only noted in + * the count of tests that ran. + */ +public class Result implements Serializable { + private static final long serialVersionUID = 1L; + private AtomicInteger fCount = new AtomicInteger(); + private AtomicInteger fIgnoreCount= new AtomicInteger(); + private final List<Failure> fFailures= Collections.synchronizedList(new ArrayList<Failure>()); + private long fRunTime= 0; + private long fStartTime; + + /** + * @return the number of tests run + */ + public int getRunCount() { + return fCount.get(); + } + + /** + * @return the number of tests that failed during the run + */ + public int getFailureCount() { + return fFailures.size(); + } + + /** + * @return the number of milliseconds it took to run the entire suite to run + */ + public long getRunTime() { + return fRunTime; + } + + /** + * @return the {@link Failure}s describing tests that failed and the problems they encountered + */ + public List<Failure> getFailures() { + return fFailures; + } + + /** + * @return the number of tests ignored during the run + */ + public int getIgnoreCount() { + return fIgnoreCount.get(); + } + + /** + * @return <code>true</code> if all tests succeeded + */ + public boolean wasSuccessful() { + return getFailureCount() == 0; + } + + private class Listener extends RunListener { + @Override + public void testRunStarted(Description description) throws Exception { + fStartTime= System.currentTimeMillis(); + } + + @Override + public void testRunFinished(Result result) throws Exception { + long endTime= System.currentTimeMillis(); + fRunTime+= endTime - fStartTime; + } + + @Override + public void testFinished(Description description) throws Exception { + fCount.getAndIncrement(); + } + + @Override + public void testFailure(Failure failure) throws Exception { + fFailures.add(failure); + } + + @Override + public void testIgnored(Description description) throws Exception { + fIgnoreCount.getAndIncrement(); + } + + @Override + public void testAssumptionFailure(Failure failure) { + // do nothing: same as passing (for 4.5; may change in 4.6) + } + } + + /** + * Internal use only. + */ + public RunListener createListener() { + return new Listener(); + } +} diff --git a/src/org/junit/runner/RunWith.java b/src/org/junit/runner/RunWith.java new file mode 100644 index 0000000..602edf0 --- /dev/null +++ b/src/org/junit/runner/RunWith.java @@ -0,0 +1,34 @@ +package org.junit.runner; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * When a class is annotated with <code>@RunWith</code> or extends a class annotated + * with <code>@RunWith</code>, JUnit will invoke the class it references to run the + * tests in that class instead of the runner built into JUnit. We added this feature late + * in development. While it seems powerful we expect the runner API to change as we learn + * how people really use it. Some of the classes that are currently internal will likely + * be refined and become public. + * + * For example, suites in JUnit 4 are built using RunWith, and a custom runner named Suite: + * + * <pre> + * @RunWith(Suite.class) + * @SuiteClasses({ATest.class, BTest.class, CTest.class}) + * public class ABCSuite { + * } + * </pre> + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +@Inherited +public @interface RunWith { + /** + * @return a Runner class (must have a constructor that takes a single Class to run) + */ + Class<? extends Runner> value(); +} diff --git a/src/org/junit/runner/Runner.java b/src/org/junit/runner/Runner.java new file mode 100644 index 0000000..39e424f --- /dev/null +++ b/src/org/junit/runner/Runner.java @@ -0,0 +1,40 @@ +package org.junit.runner; + +import org.junit.runner.notification.RunNotifier; + +/** + * A <code>Runner</code> runs tests and notifies a {@link org.junit.runner.notification.RunNotifier} + * of significant events as it does so. You will need to subclass <code>Runner</code> + * when using {@link org.junit.runner.RunWith} to invoke a custom runner. When creating + * a custom runner, in addition to implementing the abstract methods here you must + * also provide a constructor that takes as an argument the {@link Class} containing + * the tests. + * <p/> + * The default runner implementation guarantees that the instances of the test case + * class will be constructed immediately before running the test and that the runner + * will retain no reference to the test case instances, generally making them + * available for garbage collection. + * + * @see org.junit.runner.Description + * @see org.junit.runner.RunWith + */ +public abstract class Runner implements Describable { + /* (non-Javadoc) + * @see org.junit.runner.Describable#getDescription() + */ + public abstract Description getDescription(); + + /** + * Run the tests for this runner. + * @param notifier will be notified of events while tests are being run--tests being + * started, finishing, and failing + */ + public abstract void run(RunNotifier notifier); + + /** + * @return the number of tests to be run by the receiver + */ + public int testCount() { + return getDescription().testCount(); + } +}
\ No newline at end of file diff --git a/src/org/junit/runner/manipulation/Filter.java b/src/org/junit/runner/manipulation/Filter.java new file mode 100644 index 0000000..c0f31b0 --- /dev/null +++ b/src/org/junit/runner/manipulation/Filter.java @@ -0,0 +1,114 @@ +package org.junit.runner.manipulation; + +import org.junit.runner.Description; +import org.junit.runner.Request; + +/** + * The canonical case of filtering is when you want to run a single test method in a class. Rather + * than introduce runner API just for that one case, JUnit provides a general filtering mechanism. + * If you want to filter the tests to be run, extend <code>Filter</code> and apply an instance of + * your filter to the {@link org.junit.runner.Request} before running it (see + * {@link org.junit.runner.JUnitCore#run(Request)}. Alternatively, apply a <code>Filter</code> to + * a {@link org.junit.runner.Runner} before running tests (for example, in conjunction with + * {@link org.junit.runner.RunWith}. + */ +public abstract class Filter { + /** + * A null <code>Filter</code> that passes all tests through. + */ + public static Filter ALL= new Filter() { + @Override + public boolean shouldRun(Description description) { + return true; + } + + @Override + public String describe() { + return "all tests"; + } + + @Override + public void apply(Object child) throws NoTestsRemainException { + // do nothing + } + + @Override + public Filter intersect(Filter second) { + return second; + } + }; + + /** + * Returns a {@code Filter} that only runs the single method described by + * {@code desiredDescription} + */ + public static Filter matchMethodDescription(final Description desiredDescription) { + return new Filter() { + @Override + public boolean shouldRun(Description description) { + if (description.isTest()) + return desiredDescription.equals(description); + + // explicitly check if any children want to run + for (Description each : description.getChildren()) + if (shouldRun(each)) + return true; + return false; + } + + @Override + public String describe() { + return String.format("Method %s", desiredDescription.getDisplayName()); + } + }; + } + + + /** + * @param description the description of the test to be run + * @return <code>true</code> if the test should be run + */ + public abstract boolean shouldRun(Description description); + + /** + * Returns a textual description of this Filter + * @return a textual description of this Filter + */ + public abstract String describe(); + + /** + * Invoke with a {@link org.junit.runner.Runner} to cause all tests it intends to run + * to first be checked with the filter. Only those that pass the filter will be run. + * @param child the runner to be filtered by the receiver + * @throws NoTestsRemainException if the receiver removes all tests + */ + public void apply(Object child) throws NoTestsRemainException { + if (!(child instanceof Filterable)) + return; + Filterable filterable= (Filterable) child; + filterable.filter(this); + } + + /** + * Returns a new Filter that accepts the intersection of the tests accepted + * by this Filter and {@code second} + */ + public Filter intersect(final Filter second) { + if (second == this || second == ALL) { + return this; + } + final Filter first= this; + return new Filter() { + @Override + public boolean shouldRun(Description description) { + return first.shouldRun(description) + && second.shouldRun(description); + } + + @Override + public String describe() { + return first.describe() + " and " + second.describe(); + } + }; + } +} diff --git a/src/org/junit/runner/manipulation/Filterable.java b/src/org/junit/runner/manipulation/Filterable.java new file mode 100644 index 0000000..782c0f7 --- /dev/null +++ b/src/org/junit/runner/manipulation/Filterable.java @@ -0,0 +1,16 @@ +package org.junit.runner.manipulation; + +/** + * Runners that allow filtering should implement this interface. Implement {@link #filter(Filter)} + * to remove tests that don't pass the filter. + */ +public interface Filterable { + + /** + * Remove tests that don't pass the parameter <code>filter</code>. + * @param filter the {@link Filter} to apply + * @throws NoTestsRemainException if all tests are filtered out + */ + void filter(Filter filter) throws NoTestsRemainException; + +} diff --git a/src/org/junit/runner/manipulation/NoTestsRemainException.java b/src/org/junit/runner/manipulation/NoTestsRemainException.java new file mode 100644 index 0000000..03fb3bf --- /dev/null +++ b/src/org/junit/runner/manipulation/NoTestsRemainException.java @@ -0,0 +1,8 @@ +package org.junit.runner.manipulation; + +/** + * Thrown when a filter removes all tests from a runner. + */ +public class NoTestsRemainException extends Exception { + private static final long serialVersionUID = 1L; +} diff --git a/src/org/junit/runner/manipulation/Sortable.java b/src/org/junit/runner/manipulation/Sortable.java new file mode 100644 index 0000000..fec1d0c --- /dev/null +++ b/src/org/junit/runner/manipulation/Sortable.java @@ -0,0 +1,17 @@ +package org.junit.runner.manipulation; + +/** + * Interface for runners that allow sorting of tests. By sorting tests based on when they last failed, most recently + * failed first, you can reduce the average time to the first test failing. Test sorting should not be used to + * cope with order dependencies between tests. Tests that are isolated from each other are less + * expensive to maintain and can be run individually. + */ +public interface Sortable { + + /** + * Sorts the tests using <code>sorter</code> + * @param sorter the {@link Sorter} to use for sorting the tests + */ + public void sort(Sorter sorter); + +} diff --git a/src/org/junit/runner/manipulation/Sorter.java b/src/org/junit/runner/manipulation/Sorter.java new file mode 100644 index 0000000..242df14 --- /dev/null +++ b/src/org/junit/runner/manipulation/Sorter.java @@ -0,0 +1,46 @@ +package org.junit.runner.manipulation; + +import java.util.Comparator; + +import org.junit.runner.Description; + +/** + * A <code>Sorter</code> orders tests. In general you will not need + * to use a <code>Sorter</code> directly. Instead, use {@link org.junit.runner.Request#sortWith(Comparator)}. + * + * + */ +public class Sorter implements Comparator<Description> { + /** + * NULL is a <code>Sorter</code> that leaves elements in an undefined order + */ + public static Sorter NULL= new Sorter(new Comparator<Description>() { + public int compare(Description o1, Description o2) { + return 0; + }}); + private final Comparator<Description> fComparator; + + /** + * Creates a <code>Sorter</code> that uses <code>comparator</code> + * to sort tests + * @param comparator the {@link Comparator} to use when sorting tests + */ + public Sorter(Comparator<Description> comparator) { + fComparator= comparator; + } + + /** + * Sorts the test in <code>runner</code> using <code>comparator</code> + * @param object + */ + public void apply(Object object) { + if (object instanceof Sortable) { + Sortable sortable = (Sortable) object; + sortable.sort(this); + } + } + + public int compare(Description o1, Description o2) { + return fComparator.compare(o1, o2); + } +} diff --git a/src/org/junit/runner/manipulation/package-info.java b/src/org/junit/runner/manipulation/package-info.java new file mode 100644 index 0000000..ba5c3b2 --- /dev/null +++ b/src/org/junit/runner/manipulation/package-info.java @@ -0,0 +1,7 @@ +/** + * Provides classes to {@link org.junit.runner.manipulation.Filter filter} or {@link org.junit.runner.manipulation.Sorter sort} tests. + * + * @since 4.0 + * @see org.junit.runner.Runner + */ +package org.junit.runner.manipulation;
\ No newline at end of file diff --git a/src/org/junit/runner/notification/Failure.java b/src/org/junit/runner/notification/Failure.java new file mode 100644 index 0000000..501caa5 --- /dev/null +++ b/src/org/junit/runner/notification/Failure.java @@ -0,0 +1,79 @@ +package org.junit.runner.notification; + +import java.io.PrintWriter; +import java.io.Serializable; +import java.io.StringWriter; + +import org.junit.runner.Description; + +/** + * A <code>Failure</code> holds a description of the failed test and the + * exception that was thrown while running it. In most cases the {@link org.junit.runner.Description} + * will be of a single test. However, if problems are encountered while constructing the + * test (for example, if a {@link org.junit.BeforeClass} method is not static), it may describe + * something other than a single test. + */ +public class Failure implements Serializable { + private static final long serialVersionUID = 1L; + private final Description fDescription; + private final Throwable fThrownException; + + /** + * Constructs a <code>Failure</code> with the given description and exception. + * @param description a {@link org.junit.runner.Description} of the test that failed + * @param thrownException the exception that was thrown while running the test + */ + public Failure(Description description, Throwable thrownException) { + fThrownException = thrownException; + fDescription= description; + } + + /** + * @return a user-understandable label for the test + */ + public String getTestHeader() { + return fDescription.getDisplayName(); + } + + /** + * @return the raw description of the context of the failure. + */ + public Description getDescription() { + return fDescription; + } + + /** + * @return the exception thrown + */ + + public Throwable getException() { + return fThrownException; + } + + @Override + public String toString() { + StringBuffer buffer= new StringBuffer(); + buffer.append(getTestHeader() + ": "+fThrownException.getMessage()); + return buffer.toString(); + } + + /** + * Convenience method + * @return the printed form of the exception + */ + public String getTrace() { + StringWriter stringWriter= new StringWriter(); + PrintWriter writer= new PrintWriter(stringWriter); + getException().printStackTrace(writer); + StringBuffer buffer= stringWriter.getBuffer(); + return buffer.toString(); + } + + /** + * Convenience method + * @return the message of the thrown exception + */ + public String getMessage() { + return getException().getMessage(); + } +} diff --git a/src/org/junit/runner/notification/RunListener.java b/src/org/junit/runner/notification/RunListener.java new file mode 100644 index 0000000..ffe8134 --- /dev/null +++ b/src/org/junit/runner/notification/RunListener.java @@ -0,0 +1,93 @@ +package org.junit.runner.notification; + +import org.junit.internal.AssumptionViolatedException; +import org.junit.runner.Description; +import org.junit.runner.Result; + +/** + * <p>If you need to respond to the events during a test run, extend <code>RunListener</code> + * and override the appropriate methods. If a listener throws an exception while processing a + * test event, it will be removed for the remainder of the test run.</p> + * + * <p>For example, suppose you have a <code>Cowbell</code> + * class that you want to make a noise whenever a test fails. You could write: + * <pre> + * public class RingingListener extends RunListener { + * public void testFailure(Failure failure) { + * Cowbell.ring(); + * } + * } + * </pre> + * </p> + * + * <p>To invoke your listener, you need to run your tests through <code>JUnitCore</code>. + * <pre> + * public void main(String... args) { + * JUnitCore core= new JUnitCore(); + * core.addListener(new RingingListener()); + * core.run(MyTestClass.class); + * } + * </pre> + * </p> + * @see org.junit.runner.JUnitCore + */ +public class RunListener { + + /** + * Called before any tests have been run. + * @param description describes the tests to be run + */ + public void testRunStarted(Description description) throws Exception { + } + + /** + * Called when all tests have finished + * @param result the summary of the test run, including all the tests that failed + */ + public void testRunFinished(Result result) throws Exception { + } + + /** + * Called when an atomic test is about to be started. + * @param description the description of the test that is about to be run + * (generally a class and method name) + */ + public void testStarted(Description description) throws Exception { + } + + /** + * Called when an atomic test has finished, whether the test succeeds or fails. + * @param description the description of the test that just ran + */ + public void testFinished(Description description) throws Exception { + } + + /** + * Called when an atomic test fails. + * @param failure describes the test that failed and the exception that was thrown + */ + public void testFailure(Failure failure) throws Exception { + } + + /** + * Called when an atomic test flags that it assumes a condition that is + * false + * + * @param failure + * describes the test that failed and the + * {@link AssumptionViolatedException} that was thrown + */ + public void testAssumptionFailure(Failure failure) { + } + + /** + * Called when a test will not be run, generally because a test method is annotated + * with {@link org.junit.Ignore}. + * + * @param description describes the test that will not be run + */ + public void testIgnored(Description description) throws Exception { + } +} + + diff --git a/src/org/junit/runner/notification/RunNotifier.java b/src/org/junit/runner/notification/RunNotifier.java new file mode 100644 index 0000000..d0f6c85 --- /dev/null +++ b/src/org/junit/runner/notification/RunNotifier.java @@ -0,0 +1,166 @@ +package org.junit.runner.notification; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; + +import org.junit.internal.AssumptionViolatedException; +import org.junit.runner.Description; +import org.junit.runner.Result; + +/** + * If you write custom runners, you may need to notify JUnit of your progress running tests. + * Do this by invoking the <code>RunNotifier</code> passed to your implementation of + * {@link org.junit.runner.Runner#run(RunNotifier)}. Future evolution of this class is likely to + * move {@link #fireTestRunStarted(Description)} and {@link #fireTestRunFinished(Result)} + * to a separate class since they should only be called once per run. + */ +public class RunNotifier { + private final List<RunListener> fListeners= + Collections.synchronizedList(new ArrayList<RunListener>()); + private boolean fPleaseStop= false; + + /** Internal use only + */ + public void addListener(RunListener listener) { + fListeners.add(listener); + } + + /** Internal use only + */ + public void removeListener(RunListener listener) { + fListeners.remove(listener); + } + + private abstract class SafeNotifier { + void run() { + synchronized (fListeners) { + for (Iterator<RunListener> all= fListeners.iterator(); all.hasNext();) + try { + notifyListener(all.next()); + } catch (Exception e) { + all.remove(); // Remove the offending listener first to avoid an infinite loop + fireTestFailure(new Failure(Description.TEST_MECHANISM, e)); + } + } + } + + abstract protected void notifyListener(RunListener each) throws Exception; + } + + /** + * Do not invoke. + */ + public void fireTestRunStarted(final Description description) { + new SafeNotifier() { + @Override + protected void notifyListener(RunListener each) throws Exception { + each.testRunStarted(description); + }; + }.run(); + } + + /** + * Do not invoke. + */ + public void fireTestRunFinished(final Result result) { + new SafeNotifier() { + @Override + protected void notifyListener(RunListener each) throws Exception { + each.testRunFinished(result); + }; + }.run(); + } + + /** + * Invoke to tell listeners that an atomic test is about to start. + * @param description the description of the atomic test (generally a class and method name) + * @throws StoppedByUserException thrown if a user has requested that the test run stop + */ + public void fireTestStarted(final Description description) throws StoppedByUserException { + if (fPleaseStop) + throw new StoppedByUserException(); + new SafeNotifier() { + @Override + protected void notifyListener(RunListener each) throws Exception { + each.testStarted(description); + }; + }.run(); + } + + /** + * Invoke to tell listeners that an atomic test failed. + * @param failure the description of the test that failed and the exception thrown + */ + public void fireTestFailure(final Failure failure) { + new SafeNotifier() { + @Override + protected void notifyListener(RunListener each) throws Exception { + each.testFailure(failure); + }; + }.run(); + } + + /** + * Invoke to tell listeners that an atomic test flagged that it assumed + * something false. + * + * @param failure + * the description of the test that failed and the + * {@link AssumptionViolatedException} thrown + */ + public void fireTestAssumptionFailed(final Failure failure) { + new SafeNotifier() { + @Override + protected void notifyListener(RunListener each) throws Exception { + each.testAssumptionFailure(failure); + }; + }.run(); + } + + /** + * Invoke to tell listeners that an atomic test was ignored. + * @param description the description of the ignored test + */ + public void fireTestIgnored(final Description description) { + new SafeNotifier() { + @Override + protected void notifyListener(RunListener each) throws Exception { + each.testIgnored(description); + } + }.run(); + } + + /** + * Invoke to tell listeners that an atomic test finished. Always invoke + * {@link #fireTestFinished(Description)} if you invoke {@link #fireTestStarted(Description)} + * as listeners are likely to expect them to come in pairs. + * @param description the description of the test that finished + */ + public void fireTestFinished(final Description description) { + new SafeNotifier() { + @Override + protected void notifyListener(RunListener each) throws Exception { + each.testFinished(description); + }; + }.run(); + } + + /** + * Ask that the tests run stop before starting the next test. Phrased politely because + * the test currently running will not be interrupted. It seems a little odd to put this + * functionality here, but the <code>RunNotifier</code> is the only object guaranteed + * to be shared amongst the many runners involved. + */ + public void pleaseStop() { + fPleaseStop= true; + } + + /** + * Internal use only. The Result's listener must be first. + */ + public void addFirstListener(RunListener listener) { + fListeners.add(0, listener); + } +}
\ No newline at end of file diff --git a/src/org/junit/runner/notification/StoppedByUserException.java b/src/org/junit/runner/notification/StoppedByUserException.java new file mode 100644 index 0000000..89be3ba --- /dev/null +++ b/src/org/junit/runner/notification/StoppedByUserException.java @@ -0,0 +1,11 @@ +package org.junit.runner.notification; + +/** + * Thrown when a user has requested that the test run stop. Writers of + * test running GUIs should be prepared to catch a <code>StoppedByUserException</code>. + * + * @see org.junit.runner.notification.RunNotifier + */ +public class StoppedByUserException extends RuntimeException { + private static final long serialVersionUID= 1L; +} diff --git a/src/org/junit/runner/notification/package-info.java b/src/org/junit/runner/notification/package-info.java new file mode 100644 index 0000000..0331c8f --- /dev/null +++ b/src/org/junit/runner/notification/package-info.java @@ -0,0 +1,6 @@ +/** + * Provides information about a test run. + * + * @since 4.0 + */ +package org.junit.runner.notification;
\ No newline at end of file diff --git a/src/org/junit/runner/package-info.java b/src/org/junit/runner/package-info.java new file mode 100644 index 0000000..e19fa0b --- /dev/null +++ b/src/org/junit/runner/package-info.java @@ -0,0 +1,6 @@ +/** + * Provides classes used to describe, collect, run and analyze multiple tests. + * + * @since 4.0 + */ +package org.junit.runner;
\ No newline at end of file diff --git a/src/org/junit/runners/AllTests.java b/src/org/junit/runners/AllTests.java new file mode 100644 index 0000000..50c02db --- /dev/null +++ b/src/org/junit/runners/AllTests.java @@ -0,0 +1,24 @@ +package org.junit.runners; + +import org.junit.internal.runners.SuiteMethod; + +/** Runner for use with JUnit 3.8.x-style AllTests classes + * (those that only implement a static <code>suite()</code> + * method). For example: + * <pre> + * @RunWith(AllTests.class) + * public class ProductTests { + * public static junit.framework.Test suite() { + * ... + * } + * } + * </pre> + */ +public class AllTests extends SuiteMethod { + /** + * Only called reflectively. Do not use programmatically. + */ + public AllTests(Class<?> klass) throws Throwable { + super(klass); + } +} diff --git a/src/org/junit/runners/BlockJUnit4ClassRunner.java b/src/org/junit/runners/BlockJUnit4ClassRunner.java new file mode 100644 index 0000000..92e0d07 --- /dev/null +++ b/src/org/junit/runners/BlockJUnit4ClassRunner.java @@ -0,0 +1,407 @@ +package org.junit.runners; + +import static org.junit.internal.runners.rules.RuleFieldValidator.RULE_VALIDATOR; + +import java.util.List; + +import org.junit.After; +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Rule; +import org.junit.Test; +import org.junit.Test.None; +import org.junit.internal.runners.model.ReflectiveCallable; +import org.junit.internal.runners.statements.ExpectException; +import org.junit.internal.runners.statements.Fail; +import org.junit.internal.runners.statements.FailOnTimeout; +import org.junit.internal.runners.statements.InvokeMethod; +import org.junit.internal.runners.statements.RunAfters; +import org.junit.internal.runners.statements.RunBefores; +import org.junit.rules.RunRules; +import org.junit.rules.TestRule; +import org.junit.runner.Description; +import org.junit.runner.notification.RunNotifier; +import org.junit.runners.model.FrameworkMethod; +import org.junit.runners.model.InitializationError; +import org.junit.runners.model.MultipleFailureException; +import org.junit.runners.model.Statement; + +/** + * Implements the JUnit 4 standard test case class model, as defined by the + * annotations in the org.junit package. Many users will never notice this + * class: it is now the default test class runner, but it should have exactly + * the same behavior as the old test class runner ({@code JUnit4ClassRunner}). + * + * BlockJUnit4ClassRunner has advantages for writers of custom JUnit runners + * that are slight changes to the default behavior, however: + * + * <ul> + * <li>It has a much simpler implementation based on {@link Statement}s, + * allowing new operations to be inserted into the appropriate point in the + * execution flow. + * + * <li>It is published, and extension and reuse are encouraged, whereas {@code + * JUnit4ClassRunner} was in an internal package, and is now deprecated. + * </ul> + */ +public class BlockJUnit4ClassRunner extends ParentRunner<FrameworkMethod> { + /** + * Creates a BlockJUnit4ClassRunner to run {@code klass} + * + * @throws InitializationError + * if the test class is malformed. + */ + public BlockJUnit4ClassRunner(Class<?> klass) throws InitializationError { + super(klass); + } + + // + // Implementation of ParentRunner + // + + @Override + protected void runChild(final FrameworkMethod method, RunNotifier notifier) { + Description description= describeChild(method); + if (method.getAnnotation(Ignore.class) != null) { + notifier.fireTestIgnored(description); + } else { + runLeaf(methodBlock(method), description, notifier); + } + } + + @Override + protected Description describeChild(FrameworkMethod method) { + return Description.createTestDescription(getTestClass().getJavaClass(), + testName(method), method.getAnnotations()); + } + + @Override + protected List<FrameworkMethod> getChildren() { + return computeTestMethods(); + } + + // + // Override in subclasses + // + + /** + * Returns the methods that run tests. Default implementation returns all + * methods annotated with {@code @Test} on this class and superclasses that + * are not overridden. + */ + protected List<FrameworkMethod> computeTestMethods() { + return getTestClass().getAnnotatedMethods(Test.class); + } + + @Override + protected void collectInitializationErrors(List<Throwable> errors) { + super.collectInitializationErrors(errors); + + validateNoNonStaticInnerClass(errors); + validateConstructor(errors); + validateInstanceMethods(errors); + validateFields(errors); + } + + protected void validateNoNonStaticInnerClass(List<Throwable> errors) { + if (getTestClass().isANonStaticInnerClass()) { + String gripe= "The inner class " + getTestClass().getName() + + " is not static."; + errors.add(new Exception(gripe)); + } + } + + /** + * Adds to {@code errors} if the test class has more than one constructor, + * or if the constructor takes parameters. Override if a subclass requires + * different validation rules. + */ + protected void validateConstructor(List<Throwable> errors) { + validateOnlyOneConstructor(errors); + validateZeroArgConstructor(errors); + } + + /** + * Adds to {@code errors} if the test class has more than one constructor + * (do not override) + */ + protected void validateOnlyOneConstructor(List<Throwable> errors) { + if (!hasOneConstructor()) { + String gripe= "Test class should have exactly one public constructor"; + errors.add(new Exception(gripe)); + } + } + + /** + * Adds to {@code errors} if the test class's single constructor takes + * parameters (do not override) + */ + protected void validateZeroArgConstructor(List<Throwable> errors) { + if (!getTestClass().isANonStaticInnerClass() + && hasOneConstructor() + && (getTestClass().getOnlyConstructor().getParameterTypes().length != 0)) { + String gripe= "Test class should have exactly one public zero-argument constructor"; + errors.add(new Exception(gripe)); + } + } + + private boolean hasOneConstructor() { + return getTestClass().getJavaClass().getConstructors().length == 1; + } + + /** + * Adds to {@code errors} for each method annotated with {@code @Test}, + * {@code @Before}, or {@code @After} that is not a public, void instance + * method with no arguments. + * + * @deprecated unused API, will go away in future version + */ + @Deprecated + protected void validateInstanceMethods(List<Throwable> errors) { + validatePublicVoidNoArgMethods(After.class, false, errors); + validatePublicVoidNoArgMethods(Before.class, false, errors); + validateTestMethods(errors); + + if (computeTestMethods().size() == 0) + errors.add(new Exception("No runnable methods")); + } + + private void validateFields(List<Throwable> errors) { + RULE_VALIDATOR.validate(getTestClass(), errors); + } + + /** + * Adds to {@code errors} for each method annotated with {@code @Test}that + * is not a public, void instance method with no arguments. + */ + protected void validateTestMethods(List<Throwable> errors) { + validatePublicVoidNoArgMethods(Test.class, false, errors); + } + + /** + * Returns a new fixture for running a test. Default implementation executes + * the test class's no-argument constructor (validation should have ensured + * one exists). + */ + protected Object createTest() throws Exception { + return getTestClass().getOnlyConstructor().newInstance(); + } + + /** + * Returns the name that describes {@code method} for {@link Description}s. + * Default implementation is the method's name + */ + protected String testName(FrameworkMethod method) { + return method.getName(); + } + + /** + * Returns a Statement that, when executed, either returns normally if + * {@code method} passes, or throws an exception if {@code method} fails. + * + * Here is an outline of the default implementation: + * + * <ul> + * <li>Invoke {@code method} on the result of {@code createTest()}, and + * throw any exceptions thrown by either operation. + * <li>HOWEVER, if {@code method}'s {@code @Test} annotation has the {@code + * expecting} attribute, return normally only if the previous step threw an + * exception of the correct type, and throw an exception otherwise. + * <li>HOWEVER, if {@code method}'s {@code @Test} annotation has the {@code + * timeout} attribute, throw an exception if the previous step takes more + * than the specified number of milliseconds. + * <li>ALWAYS run all non-overridden {@code @Before} methods on this class + * and superclasses before any of the previous steps; if any throws an + * Exception, stop execution and pass the exception on. + * <li>ALWAYS run all non-overridden {@code @After} methods on this class + * and superclasses after any of the previous steps; all After methods are + * always executed: exceptions thrown by previous steps are combined, if + * necessary, with exceptions from After methods into a + * {@link MultipleFailureException}. + * <li>ALWAYS allow {@code @Rule} fields to modify the execution of the + * above steps. A {@code Rule} may prevent all execution of the above steps, + * or add additional behavior before and after, or modify thrown exceptions. + * For more information, see {@link TestRule} + * </ul> + * + * This can be overridden in subclasses, either by overriding this method, + * or the implementations creating each sub-statement. + */ + protected Statement methodBlock(FrameworkMethod method) { + Object test; + try { + test= new ReflectiveCallable() { + @Override + protected Object runReflectiveCall() throws Throwable { + return createTest(); + } + }.run(); + } catch (Throwable e) { + return new Fail(e); + } + + Statement statement= methodInvoker(method, test); + statement= possiblyExpectingExceptions(method, test, statement); + statement= withPotentialTimeout(method, test, statement); + statement= withBefores(method, test, statement); + statement= withAfters(method, test, statement); + statement= withRules(method, test, statement); + return statement; + } + + // + // Statement builders + // + + /** + * Returns a {@link Statement} that invokes {@code method} on {@code test} + */ + protected Statement methodInvoker(FrameworkMethod method, Object test) { + return new InvokeMethod(method, test); + } + + /** + * Returns a {@link Statement}: if {@code method}'s {@code @Test} annotation + * has the {@code expecting} attribute, return normally only if {@code next} + * throws an exception of the correct type, and throw an exception + * otherwise. + * + * @deprecated Will be private soon: use Rules instead + */ + @Deprecated + protected Statement possiblyExpectingExceptions(FrameworkMethod method, + Object test, Statement next) { + Test annotation= method.getAnnotation(Test.class); + return expectsException(annotation) ? new ExpectException(next, + getExpectedException(annotation)) : next; + } + + /** + * Returns a {@link Statement}: if {@code method}'s {@code @Test} annotation + * has the {@code timeout} attribute, throw an exception if {@code next} + * takes more than the specified number of milliseconds. + * + * @deprecated Will be private soon: use Rules instead + */ + @Deprecated + protected Statement withPotentialTimeout(FrameworkMethod method, + Object test, Statement next) { + long timeout= getTimeout(method.getAnnotation(Test.class)); + return timeout > 0 ? new FailOnTimeout(next, timeout) : next; + } + + /** + * Returns a {@link Statement}: run all non-overridden {@code @Before} + * methods on this class and superclasses before running {@code next}; if + * any throws an Exception, stop execution and pass the exception on. + * + * @deprecated Will be private soon: use Rules instead + */ + @Deprecated + protected Statement withBefores(FrameworkMethod method, Object target, + Statement statement) { + List<FrameworkMethod> befores= getTestClass().getAnnotatedMethods( + Before.class); + return befores.isEmpty() ? statement : new RunBefores(statement, + befores, target); + } + + /** + * Returns a {@link Statement}: run all non-overridden {@code @After} + * methods on this class and superclasses before running {@code next}; all + * After methods are always executed: exceptions thrown by previous steps + * are combined, if necessary, with exceptions from After methods into a + * {@link MultipleFailureException}. + * + * @deprecated Will be private soon: use Rules instead + */ + @Deprecated + protected Statement withAfters(FrameworkMethod method, Object target, + Statement statement) { + List<FrameworkMethod> afters= getTestClass().getAnnotatedMethods( + After.class); + return afters.isEmpty() ? statement : new RunAfters(statement, afters, + target); + } + + private Statement withRules(FrameworkMethod method, Object target, + Statement statement) { + Statement result= statement; + result= withMethodRules(method, target, result); + result= withTestRules(method, target, result); + return result; + } + + @SuppressWarnings("deprecation") + private Statement withMethodRules(FrameworkMethod method, Object target, + Statement result) { + List<TestRule> testRules= getTestRules(target); + for (org.junit.rules.MethodRule each : getMethodRules(target)) + if (! testRules.contains(each)) + result= each.apply(result, method, target); + return result; + } + + @SuppressWarnings("deprecation") + private List<org.junit.rules.MethodRule> getMethodRules(Object target) { + return rules(target); + } + + /** + * @param target + * the test case instance + * @return a list of MethodRules that should be applied when executing this + * test + * @deprecated {@link org.junit.rules.MethodRule} is a deprecated interface. Port to + * {@link TestRule} and + * {@link BlockJUnit4ClassRunner#getTestRules(Object)} + */ + @Deprecated + protected List<org.junit.rules.MethodRule> rules(Object target) { + return getTestClass().getAnnotatedFieldValues(target, Rule.class, + org.junit.rules.MethodRule.class); + } + + /** + * Returns a {@link Statement}: apply all non-static {@link Value} fields + * annotated with {@link Rule}. + * + * @param statement The base statement + * @return a RunRules statement if any class-level {@link Rule}s are + * found, or the base statement + */ + private Statement withTestRules(FrameworkMethod method, Object target, + Statement statement) { + List<TestRule> testRules= getTestRules(target); + return testRules.isEmpty() ? statement : + new RunRules(statement, testRules, describeChild(method)); + } + + /** + * @param target + * the test case instance + * @return a list of TestRules that should be applied when executing this + * test + */ + protected List<TestRule> getTestRules(Object target) { + return getTestClass().getAnnotatedFieldValues(target, + Rule.class, TestRule.class); + } + + private Class<? extends Throwable> getExpectedException(Test annotation) { + if (annotation == null || annotation.expected() == None.class) + return null; + else + return annotation.expected(); + } + + private boolean expectsException(Test annotation) { + return getExpectedException(annotation) != null; + } + + private long getTimeout(Test annotation) { + if (annotation == null) + return 0; + return annotation.timeout(); + } +} diff --git a/src/org/junit/runners/JUnit4.java b/src/org/junit/runners/JUnit4.java new file mode 100644 index 0000000..1e1f347 --- /dev/null +++ b/src/org/junit/runners/JUnit4.java @@ -0,0 +1,22 @@ +package org.junit.runners; + +import org.junit.runners.model.InitializationError; + +/** + * Aliases the current default JUnit 4 class runner, for future-proofing. If + * future versions of JUnit change the default Runner class, they will also + * change the definition of this class. Developers wanting to explicitly tag a + * class as a JUnit 4 class should use {@code @RunWith(JUnit4.class)}, not, + * for example in JUnit 4.5, {@code @RunWith(BlockJUnit4ClassRunner.class)}. + * This is the only way this class should be used--any extension that + * depends on the implementation details of this class is likely to break + * in future versions. + */ +public final class JUnit4 extends BlockJUnit4ClassRunner { + /** + * Constructs a new instance of the default runner + */ + public JUnit4(Class<?> klass) throws InitializationError { + super(klass); + } +} diff --git a/src/org/junit/runners/Parameterized.java b/src/org/junit/runners/Parameterized.java new file mode 100644 index 0000000..3ebfead --- /dev/null +++ b/src/org/junit/runners/Parameterized.java @@ -0,0 +1,167 @@ +package org.junit.runners; + +import java.lang.annotation.Annotation; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.junit.runner.Runner; +import org.junit.runner.notification.RunNotifier; +import org.junit.runners.model.FrameworkMethod; +import org.junit.runners.model.InitializationError; +import org.junit.runners.model.Statement; +import org.junit.runners.model.TestClass; + +/** + * <p> + * The custom runner <code>Parameterized</code> implements parameterized tests. + * When running a parameterized test class, instances are created for the + * cross-product of the test methods and the test data elements. + * </p> + * + * For example, to test a Fibonacci function, write: + * + * <pre> + * @RunWith(Parameterized.class) + * public class FibonacciTest { + * @Parameters + * public static List<Object[]> data() { + * return Arrays.asList(new Object[][] { + * { 0, 0 }, { 1, 1 }, { 2, 1 }, { 3, 2 }, { 4, 3 }, { 5, 5 }, { 6, 8 } + * }); + * } + * + * private int fInput; + * + * private int fExpected; + * + * public FibonacciTest(int input, int expected) { + * fInput= input; + * fExpected= expected; + * } + * + * @Test + * public void test() { + * assertEquals(fExpected, Fibonacci.compute(fInput)); + * } + * } + * </pre> + * + * <p> + * Each instance of <code>FibonacciTest</code> will be constructed using the + * two-argument constructor and the data values in the + * <code>@Parameters</code> method. + * </p> + */ +public class Parameterized extends Suite { + /** + * Annotation for a method which provides parameters to be injected into the + * test class constructor by <code>Parameterized</code> + */ + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.METHOD) + public static @interface Parameters { + } + + private class TestClassRunnerForParameters extends + BlockJUnit4ClassRunner { + private final int fParameterSetNumber; + + private final List<Object[]> fParameterList; + + TestClassRunnerForParameters(Class<?> type, + List<Object[]> parameterList, int i) throws InitializationError { + super(type); + fParameterList= parameterList; + fParameterSetNumber= i; + } + + @Override + public Object createTest() throws Exception { + return getTestClass().getOnlyConstructor().newInstance( + computeParams()); + } + + private Object[] computeParams() throws Exception { + try { + return fParameterList.get(fParameterSetNumber); + } catch (ClassCastException e) { + throw new Exception(String.format( + "%s.%s() must return a Collection of arrays.", + getTestClass().getName(), getParametersMethod( + getTestClass()).getName())); + } + } + + @Override + protected String getName() { + return String.format("[%s]", fParameterSetNumber); + } + + @Override + protected String testName(final FrameworkMethod method) { + return String.format("%s[%s]", method.getName(), + fParameterSetNumber); + } + + @Override + protected void validateConstructor(List<Throwable> errors) { + validateOnlyOneConstructor(errors); + } + + @Override + protected Statement classBlock(RunNotifier notifier) { + return childrenInvoker(notifier); + } + + @Override + protected Annotation[] getRunnerAnnotations() { + return new Annotation[0]; + } + } + + private final ArrayList<Runner> runners= new ArrayList<Runner>(); + + /** + * Only called reflectively. Do not use programmatically. + */ + public Parameterized(Class<?> klass) throws Throwable { + super(klass, Collections.<Runner>emptyList()); + List<Object[]> parametersList= getParametersList(getTestClass()); + for (int i= 0; i < parametersList.size(); i++) + runners.add(new TestClassRunnerForParameters(getTestClass().getJavaClass(), + parametersList, i)); + } + + @Override + protected List<Runner> getChildren() { + return runners; + } + + @SuppressWarnings("unchecked") + private List<Object[]> getParametersList(TestClass klass) + throws Throwable { + return (List<Object[]>) getParametersMethod(klass).invokeExplosively( + null); + } + + private FrameworkMethod getParametersMethod(TestClass testClass) + throws Exception { + List<FrameworkMethod> methods= testClass + .getAnnotatedMethods(Parameters.class); + for (FrameworkMethod each : methods) { + int modifiers= each.getMethod().getModifiers(); + if (Modifier.isStatic(modifiers) && Modifier.isPublic(modifiers)) + return each; + } + + throw new Exception("No public static parameters method on class " + + testClass.getName()); + } + +} diff --git a/src/org/junit/runners/ParentRunner.java b/src/org/junit/runners/ParentRunner.java new file mode 100644 index 0000000..a41aad3 --- /dev/null +++ b/src/org/junit/runners/ParentRunner.java @@ -0,0 +1,378 @@ +package org.junit.runners; + +import static org.junit.internal.runners.rules.RuleFieldValidator.CLASS_RULE_VALIDATOR; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; + +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.internal.AssumptionViolatedException; +import org.junit.internal.runners.model.EachTestNotifier; +import org.junit.internal.runners.statements.RunAfters; +import org.junit.internal.runners.statements.RunBefores; +import org.junit.rules.RunRules; +import org.junit.rules.TestRule; +import org.junit.runner.Description; +import org.junit.runner.Runner; +import org.junit.runner.manipulation.Filter; +import org.junit.runner.manipulation.Filterable; +import org.junit.runner.manipulation.NoTestsRemainException; +import org.junit.runner.manipulation.Sortable; +import org.junit.runner.manipulation.Sorter; +import org.junit.runner.notification.RunNotifier; +import org.junit.runner.notification.StoppedByUserException; +import org.junit.runners.model.FrameworkMethod; +import org.junit.runners.model.InitializationError; +import org.junit.runners.model.MultipleFailureException; +import org.junit.runners.model.RunnerScheduler; +import org.junit.runners.model.Statement; +import org.junit.runners.model.TestClass; + +/** + * Provides most of the functionality specific to a Runner that implements a + * "parent node" in the test tree, with children defined by objects of some data + * type {@code T}. (For {@link BlockJUnit4ClassRunner}, {@code T} is + * {@link Method} . For {@link Suite}, {@code T} is {@link Class}.) Subclasses + * must implement finding the children of the node, describing each child, and + * running each child. ParentRunner will filter and sort children, handle + * {@code @BeforeClass} and {@code @AfterClass} methods, + * handle annotated {@link ClassRule}s, create a composite + * {@link Description}, and run children sequentially. + */ +public abstract class ParentRunner<T> extends Runner implements Filterable, + Sortable { + private final TestClass fTestClass; + + private Sorter fSorter= Sorter.NULL; + + private List<T> fFilteredChildren= null; + + private RunnerScheduler fScheduler= new RunnerScheduler() { + public void schedule(Runnable childStatement) { + childStatement.run(); + } + + public void finished() { + // do nothing + } + }; + + /** + * Constructs a new {@code ParentRunner} that will run {@code @TestClass} + * @throws InitializationError + */ + protected ParentRunner(Class<?> testClass) throws InitializationError { + fTestClass= new TestClass(testClass); + validate(); + } + + // + // Must be overridden + // + + /** + * Returns a list of objects that define the children of this Runner. + */ + protected abstract List<T> getChildren(); + + /** + * Returns a {@link Description} for {@code child}, which can be assumed to + * be an element of the list returned by {@link ParentRunner#getChildren()} + */ + protected abstract Description describeChild(T child); + + /** + * Runs the test corresponding to {@code child}, which can be assumed to be + * an element of the list returned by {@link ParentRunner#getChildren()}. + * Subclasses are responsible for making sure that relevant test events are + * reported through {@code notifier} + */ + protected abstract void runChild(T child, RunNotifier notifier); + + // + // May be overridden + // + + /** + * Adds to {@code errors} a throwable for each problem noted with the test class (available from {@link #getTestClass()}). + * Default implementation adds an error for each method annotated with + * {@code @BeforeClass} or {@code @AfterClass} that is not + * {@code public static void} with no arguments. + */ + protected void collectInitializationErrors(List<Throwable> errors) { + validatePublicVoidNoArgMethods(BeforeClass.class, true, errors); + validatePublicVoidNoArgMethods(AfterClass.class, true, errors); + validateClassRules(errors); + } + + /** + * Adds to {@code errors} if any method in this class is annotated with + * {@code annotation}, but: + * <ul> + * <li>is not public, or + * <li>takes parameters, or + * <li>returns something other than void, or + * <li>is static (given {@code isStatic is false}), or + * <li>is not static (given {@code isStatic is true}). + */ + protected void validatePublicVoidNoArgMethods(Class<? extends Annotation> annotation, + boolean isStatic, List<Throwable> errors) { + List<FrameworkMethod> methods= getTestClass().getAnnotatedMethods(annotation); + + for (FrameworkMethod eachTestMethod : methods) + eachTestMethod.validatePublicVoidNoArg(isStatic, errors); + } + + private void validateClassRules(List<Throwable> errors) { + CLASS_RULE_VALIDATOR.validate(getTestClass(), errors); + } + + /** + * Constructs a {@code Statement} to run all of the tests in the test class. Override to add pre-/post-processing. + * Here is an outline of the implementation: + * <ul> + * <li>Call {@link #runChild(Object, RunNotifier)} on each object returned by {@link #getChildren()} (subject to any imposed filter and sort).</li> + * <li>ALWAYS run all non-overridden {@code @BeforeClass} methods on this class + * and superclasses before the previous step; if any throws an + * Exception, stop execution and pass the exception on. + * <li>ALWAYS run all non-overridden {@code @AfterClass} methods on this class + * and superclasses before any of the previous steps; all AfterClass methods are + * always executed: exceptions thrown by previous steps are combined, if + * necessary, with exceptions from AfterClass methods into a + * {@link MultipleFailureException}. + * </ul> + * @param notifier + * @return {@code Statement} + */ + protected Statement classBlock(final RunNotifier notifier) { + Statement statement= childrenInvoker(notifier); + statement= withBeforeClasses(statement); + statement= withAfterClasses(statement); + statement= withClassRules(statement); + return statement; + } + + /** + * Returns a {@link Statement}: run all non-overridden {@code @BeforeClass} methods on this class + * and superclasses before executing {@code statement}; if any throws an + * Exception, stop execution and pass the exception on. + */ + protected Statement withBeforeClasses(Statement statement) { + List<FrameworkMethod> befores= fTestClass + .getAnnotatedMethods(BeforeClass.class); + return befores.isEmpty() ? statement : + new RunBefores(statement, befores, null); + } + + /** + * Returns a {@link Statement}: run all non-overridden {@code @AfterClass} methods on this class + * and superclasses before executing {@code statement}; all AfterClass methods are + * always executed: exceptions thrown by previous steps are combined, if + * necessary, with exceptions from AfterClass methods into a + * {@link MultipleFailureException}. + */ + protected Statement withAfterClasses(Statement statement) { + List<FrameworkMethod> afters= fTestClass + .getAnnotatedMethods(AfterClass.class); + return afters.isEmpty() ? statement : + new RunAfters(statement, afters, null); + } + + /** + * Returns a {@link Statement}: apply all + * static fields assignable to {@link TestRule} + * annotated with {@link ClassRule}. + * + * @param statement + * the base statement + * @return a RunRules statement if any class-level {@link Rule}s are + * found, or the base statement + */ + private Statement withClassRules(Statement statement) { + List<TestRule> classRules= classRules(); + return classRules.isEmpty() ? statement : + new RunRules(statement, classRules, getDescription()); + } + + /** + * @return the {@code ClassRule}s that can transform the block that runs + * each method in the tested class. + */ + protected List<TestRule> classRules() { + return fTestClass.getAnnotatedFieldValues(null, ClassRule.class, TestRule.class); + } + + /** + * Returns a {@link Statement}: Call {@link #runChild(Object, RunNotifier)} + * on each object returned by {@link #getChildren()} (subject to any imposed + * filter and sort) + */ + protected Statement childrenInvoker(final RunNotifier notifier) { + return new Statement() { + @Override + public void evaluate() { + runChildren(notifier); + } + }; + } + + private void runChildren(final RunNotifier notifier) { + for (final T each : getFilteredChildren()) + fScheduler.schedule(new Runnable() { + public void run() { + ParentRunner.this.runChild(each, notifier); + } + }); + fScheduler.finished(); + } + + /** + * Returns a name used to describe this Runner + */ + protected String getName() { + return fTestClass.getName(); + } + + // + // Available for subclasses + // + + /** + * Returns a {@link TestClass} object wrapping the class to be executed. + */ + public final TestClass getTestClass() { + return fTestClass; + } + + /** + * Runs a {@link Statement} that represents a leaf (aka atomic) test. + */ + protected final void runLeaf(Statement statement, Description description, + RunNotifier notifier) { + EachTestNotifier eachNotifier= new EachTestNotifier(notifier, description); + eachNotifier.fireTestStarted(); + try { + statement.evaluate(); + } catch (AssumptionViolatedException e) { + eachNotifier.addFailedAssumption(e); + } catch (Throwable e) { + eachNotifier.addFailure(e); + } finally { + eachNotifier.fireTestFinished(); + } + } + + /** + * @return the annotations that should be attached to this runner's + * description. + */ + protected Annotation[] getRunnerAnnotations() { + return fTestClass.getAnnotations(); + } + + // + // Implementation of Runner + // + + @Override + public Description getDescription() { + Description description= Description.createSuiteDescription(getName(), + getRunnerAnnotations()); + for (T child : getFilteredChildren()) + description.addChild(describeChild(child)); + return description; + } + + @Override + public void run(final RunNotifier notifier) { + EachTestNotifier testNotifier= new EachTestNotifier(notifier, + getDescription()); + try { + Statement statement= classBlock(notifier); + statement.evaluate(); + } catch (AssumptionViolatedException e) { + testNotifier.fireTestIgnored(); + } catch (StoppedByUserException e) { + throw e; + } catch (Throwable e) { + testNotifier.addFailure(e); + } + } + + // + // Implementation of Filterable and Sortable + // + + public void filter(Filter filter) throws NoTestsRemainException { + for (Iterator<T> iter = getFilteredChildren().iterator(); iter.hasNext(); ) { + T each = iter.next(); + if (shouldRun(filter, each)) + try { + filter.apply(each); + } catch (NoTestsRemainException e) { + iter.remove(); + } + else + iter.remove(); + } + if (getFilteredChildren().isEmpty()) { + throw new NoTestsRemainException(); + } + } + + public void sort(Sorter sorter) { + fSorter= sorter; + for (T each : getFilteredChildren()) + sortChild(each); + Collections.sort(getFilteredChildren(), comparator()); + } + + // + // Private implementation + // + + private void validate() throws InitializationError { + List<Throwable> errors= new ArrayList<Throwable>(); + collectInitializationErrors(errors); + if (!errors.isEmpty()) + throw new InitializationError(errors); + } + + private List<T> getFilteredChildren() { + if (fFilteredChildren == null) + fFilteredChildren = new ArrayList<T>(getChildren()); + return fFilteredChildren; + } + + private void sortChild(T child) { + fSorter.apply(child); + } + + private boolean shouldRun(Filter filter, T each) { + return filter.shouldRun(describeChild(each)); + } + + private Comparator<? super T> comparator() { + return new Comparator<T>() { + public int compare(T o1, T o2) { + return fSorter.compare(describeChild(o1), describeChild(o2)); + } + }; + } + + /** + * Sets a scheduler that determines the order and parallelization + * of children. Highly experimental feature that may change. + */ + public void setScheduler(RunnerScheduler scheduler) { + this.fScheduler = scheduler; + } +} diff --git a/src/org/junit/runners/Suite.java b/src/org/junit/runners/Suite.java new file mode 100644 index 0000000..1b3bb48 --- /dev/null +++ b/src/org/junit/runners/Suite.java @@ -0,0 +1,130 @@ +package org.junit.runners; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +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.RunnerBuilder; + +/** + * Using <code>Suite</code> as a runner allows you to manually + * build a suite containing tests from many classes. It is the JUnit 4 equivalent of the JUnit 3.8.x + * static {@link junit.framework.Test} <code>suite()</code> method. To use it, annotate a class + * with <code>@RunWith(Suite.class)</code> and <code>@SuiteClasses({TestClass1.class, ...})</code>. + * When you run this class, it will run all the tests in all the suite classes. + */ +public class Suite extends ParentRunner<Runner> { + /** + * Returns an empty suite. + */ + public static Runner emptySuite() { + try { + return new Suite((Class<?>)null, new Class<?>[0]); + } catch (InitializationError e) { + throw new RuntimeException("This shouldn't be possible"); + } + } + + /** + * The <code>SuiteClasses</code> annotation specifies the classes to be run when a class + * annotated with <code>@RunWith(Suite.class)</code> is run. + */ + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.TYPE) + @Inherited + public @interface SuiteClasses { + /** + * @return the classes to be run + */ + public Class<?>[] value(); + } + + private static Class<?>[] getAnnotatedClasses(Class<?> klass) throws InitializationError { + SuiteClasses annotation= klass.getAnnotation(SuiteClasses.class); + if (annotation == null) + throw new InitializationError(String.format("class '%s' must have a SuiteClasses annotation", klass.getName())); + return annotation.value(); + } + + private final List<Runner> fRunners; + + /** + * Called reflectively on classes annotated with <code>@RunWith(Suite.class)</code> + * + * @param klass the root class + * @param builder builds runners for classes in the suite + * @throws InitializationError + */ + public Suite(Class<?> klass, RunnerBuilder builder) throws InitializationError { + this(builder, klass, getAnnotatedClasses(klass)); + } + + /** + * Call this when there is no single root class (for example, multiple class names + * passed on the command line to {@link org.junit.runner.JUnitCore} + * + * @param builder builds runners for classes in the suite + * @param classes the classes in the suite + * @throws InitializationError + */ + public Suite(RunnerBuilder builder, Class<?>[] classes) throws InitializationError { + this(null, builder.runners(null, classes)); + } + + /** + * Call this when the default builder is good enough. Left in for compatibility with JUnit 4.4. + * @param klass the root of the suite + * @param suiteClasses the classes in the suite + * @throws InitializationError + */ + protected Suite(Class<?> klass, Class<?>[] suiteClasses) throws InitializationError { + this(new AllDefaultPossibilitiesBuilder(true), klass, suiteClasses); + } + + /** + * Called by this class and subclasses once the classes making up the suite have been determined + * + * @param builder builds runners for classes in the suite + * @param klass the root of the suite + * @param suiteClasses the classes in the suite + * @throws InitializationError + */ + protected Suite(RunnerBuilder builder, Class<?> klass, Class<?>[] suiteClasses) throws InitializationError { + this(klass, builder.runners(klass, suiteClasses)); + } + + /** + * Called by this class and subclasses once the runners making up the suite have been determined + * + * @param klass root of the suite + * @param runners for each class in the suite, a {@link Runner} + * @throws InitializationError + */ + protected Suite(Class<?> klass, List<Runner> runners) throws InitializationError { + super(klass); + fRunners = runners; + } + + @Override + protected List<Runner> getChildren() { + return fRunners; + } + + @Override + protected Description describeChild(Runner child) { + return child.getDescription(); + } + + @Override + protected void runChild(Runner runner, final RunNotifier notifier) { + runner.run(notifier); + } +} diff --git a/src/org/junit/runners/model/FrameworkField.java b/src/org/junit/runners/model/FrameworkField.java new file mode 100644 index 0000000..4a4d4a4 --- /dev/null +++ b/src/org/junit/runners/model/FrameworkField.java @@ -0,0 +1,65 @@ +package org.junit.runners.model; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; + +import org.junit.runners.BlockJUnit4ClassRunner; + +/** + * Represents a field on a test class (currently used only for Rules in + * {@link BlockJUnit4ClassRunner}, but custom runners can make other uses) + */ +public class FrameworkField extends FrameworkMember<FrameworkField> { + private final Field fField; + + FrameworkField(Field field) { + fField= field; + } + + public String getName() { + return getField().getName(); + } + + @Override + public Annotation[] getAnnotations() { + return fField.getAnnotations(); + } + + public boolean isPublic() { + int modifiers= fField.getModifiers(); + return Modifier.isPublic(modifiers); + } + + @Override + public boolean isShadowedBy(FrameworkField otherMember) { + return otherMember.getName().equals(getName()); + } + + public boolean isStatic() { + int modifiers= fField.getModifiers(); + return Modifier.isStatic(modifiers); + } + + /** + * @return the underlying java Field + */ + public Field getField() { + return fField; + } + + /** + * @return the underlying Java Field type + * @see java.lang.reflect.Field#getType() + */ + public Class<?> getType() { + return fField.getType(); + } + + /** + * Attempts to retrieve the value of this field on {@code target} + */ + public Object get(Object target) throws IllegalArgumentException, IllegalAccessException { + return fField.get(target); + } +} diff --git a/src/org/junit/runners/model/FrameworkMember.java b/src/org/junit/runners/model/FrameworkMember.java new file mode 100644 index 0000000..9cccd4b --- /dev/null +++ b/src/org/junit/runners/model/FrameworkMember.java @@ -0,0 +1,20 @@ +package org.junit.runners.model; + +import java.lang.annotation.Annotation; +import java.util.List; + +abstract class FrameworkMember<T extends FrameworkMember<T>> { + /** + * Returns the annotations on this method + */ + abstract Annotation[] getAnnotations(); + + abstract boolean isShadowedBy(T otherMember); + + boolean isShadowedBy(List<T> members) { + for (T each : members) + if (isShadowedBy(each)) + return true; + return false; + } +} diff --git a/src/org/junit/runners/model/FrameworkMethod.java b/src/org/junit/runners/model/FrameworkMethod.java new file mode 100644 index 0000000..81c8963 --- /dev/null +++ b/src/org/junit/runners/model/FrameworkMethod.java @@ -0,0 +1,156 @@ +package org.junit.runners.model; + +import java.lang.annotation.Annotation; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.lang.reflect.Type; +import java.util.List; + +import org.junit.internal.runners.model.ReflectiveCallable; + +/** + * Represents a method on a test class to be invoked at the appropriate point in + * test execution. These methods are usually marked with an annotation (such as + * {@code @Test}, {@code @Before}, {@code @After}, {@code @BeforeClass}, + * {@code @AfterClass}, etc.) + */ +public class FrameworkMethod extends FrameworkMember<FrameworkMethod> { + final Method fMethod; + + /** + * Returns a new {@code FrameworkMethod} for {@code method} + */ + public FrameworkMethod(Method method) { + fMethod= method; + } + + /** + * Returns the underlying Java method + */ + public Method getMethod() { + return fMethod; + } + + /** + * Returns the result of invoking this method on {@code target} with + * parameters {@code params}. {@link InvocationTargetException}s thrown are + * unwrapped, and their causes rethrown. + */ + public Object invokeExplosively(final Object target, final Object... params) + throws Throwable { + return new ReflectiveCallable() { + @Override + protected Object runReflectiveCall() throws Throwable { + return fMethod.invoke(target, params); + } + }.run(); + } + + /** + * Returns the method's name + */ + public String getName() { + return fMethod.getName(); + } + + /** + * Adds to {@code errors} if this method: + * <ul> + * <li>is not public, or + * <li>takes parameters, or + * <li>returns something other than void, or + * <li>is static (given {@code isStatic is false}), or + * <li>is not static (given {@code isStatic is true}). + */ + public void validatePublicVoidNoArg(boolean isStatic, List<Throwable> errors) { + validatePublicVoid(isStatic, errors); + if (fMethod.getParameterTypes().length != 0) + errors.add(new Exception("Method " + fMethod.getName() + " should have no parameters")); + } + + + /** + * Adds to {@code errors} if this method: + * <ul> + * <li>is not public, or + * <li>returns something other than void, or + * <li>is static (given {@code isStatic is false}), or + * <li>is not static (given {@code isStatic is true}). + */ + public void validatePublicVoid(boolean isStatic, List<Throwable> errors) { + if (Modifier.isStatic(fMethod.getModifiers()) != isStatic) { + String state= isStatic ? "should" : "should not"; + errors.add(new Exception("Method " + fMethod.getName() + "() " + state + " be static")); + } + if (!Modifier.isPublic(fMethod.getDeclaringClass().getModifiers())) + errors.add(new Exception("Class " + fMethod.getDeclaringClass().getName() + " should be public")); + if (!Modifier.isPublic(fMethod.getModifiers())) + errors.add(new Exception("Method " + fMethod.getName() + "() should be public")); + if (fMethod.getReturnType() != Void.TYPE) + errors.add(new Exception("Method " + fMethod.getName() + "() should be void")); + } + + public void validateNoTypeParametersOnArgs(List<Throwable> errors) { + new NoGenericTypeParametersValidator(fMethod).validate(errors); + } + + @Override + public boolean isShadowedBy(FrameworkMethod other) { + if (!other.getName().equals(getName())) + return false; + if (other.getParameterTypes().length != getParameterTypes().length) + return false; + for (int i= 0; i < other.getParameterTypes().length; i++) + if (!other.getParameterTypes()[i].equals(getParameterTypes()[i])) + return false; + return true; + } + + @Override + public boolean equals(Object obj) { + if (!FrameworkMethod.class.isInstance(obj)) + return false; + return ((FrameworkMethod) obj).fMethod.equals(fMethod); + } + + @Override + public int hashCode() { + return fMethod.hashCode(); + } + + /** + * Returns true iff this is a no-arg method that returns a value assignable + * to {@code type} + * + * @deprecated This is used only by the Theories runner, and does not + * use all the generic type info that it ought to. It will be replaced + * with a forthcoming ParameterSignature#canAcceptResultOf(FrameworkMethod) + * once Theories moves to junit-contrib. + */ + @Deprecated + public boolean producesType(Type type) { + return getParameterTypes().length == 0 && type instanceof Class<?> + && ((Class<?>) type).isAssignableFrom(fMethod.getReturnType()); + } + + private Class<?>[] getParameterTypes() { + return fMethod.getParameterTypes(); + } + + /** + * Returns the annotations on this method + */ + @Override + public Annotation[] getAnnotations() { + return fMethod.getAnnotations(); + } + + /** + * Returns the annotation of type {@code annotationType} on this method, if + * one exists. + */ + public <T extends Annotation> T getAnnotation(Class<T> annotationType) { + return fMethod.getAnnotation(annotationType); + } +} diff --git a/src/org/junit/runners/model/InitializationError.java b/src/org/junit/runners/model/InitializationError.java new file mode 100644 index 0000000..4de9ea7 --- /dev/null +++ b/src/org/junit/runners/model/InitializationError.java @@ -0,0 +1,39 @@ +package org.junit.runners.model; + +import java.util.Arrays; +import java.util.List; + +/** + * Represents one or more problems encountered while initializing a Runner + */ +public class InitializationError extends Exception { + private static final long serialVersionUID= 1L; + private final List<Throwable> fErrors; + + /** + * Construct a new {@code InitializationError} with one or more + * errors {@code errors} as causes + */ + public InitializationError(List<Throwable> errors) { + fErrors= errors; + } + + public InitializationError(Throwable error) { + this(Arrays.asList(error)); + } + + /** + * Construct a new {@code InitializationError} with one cause + * with message {@code string} + */ + public InitializationError(String string) { + this(new Exception(string)); + } + + /** + * Returns one or more Throwables that led to this initialization error. + */ + public List<Throwable> getCauses() { + return fErrors; + } +} diff --git a/src/org/junit/runners/model/MultipleFailureException.java b/src/org/junit/runners/model/MultipleFailureException.java new file mode 100644 index 0000000..6d70ca0 --- /dev/null +++ b/src/org/junit/runners/model/MultipleFailureException.java @@ -0,0 +1,60 @@ +// Copyright 2010 Google Inc. All Rights Reserved. + +package org.junit.runners.model; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Collects multiple {@code Throwable}s into one exception. + */ +public class MultipleFailureException extends Exception { + private static final long serialVersionUID= 1L; + + private final List<Throwable> fErrors; + + public MultipleFailureException(List<Throwable> errors) { + fErrors= new ArrayList<Throwable>(errors); + } + + public List<Throwable> getFailures() { + return Collections.unmodifiableList(fErrors); + } + + @Override + public String getMessage() { + StringBuilder sb = new StringBuilder( + String.format("There were %d errors:", fErrors.size())); + for (Throwable e : fErrors) { + sb.append(String.format("\n %s(%s)", e.getClass().getName(), e.getMessage())); + } + return sb.toString(); + } + + /** + * Asserts that a list of throwables is empty. If it isn't empty, + * will throw {@link MultipleFailureException} (if there are + * multiple throwables in the list) or the first element in the list + * (if there is only one element). + * + * @param errors list to check + * @throws Throwable if the list is not empty + */ + @SuppressWarnings("deprecation") + public static void assertEmpty(List<Throwable> errors) throws Throwable { + if (errors.isEmpty()) + return; + if (errors.size() == 1) + throw errors.get(0); + + /* + * Many places in the code are documented to throw + * org.junit.internal.runners.model.MultipleFailureException. + * That class now extends this one, so we throw the internal + * exception in case developers have tests that catch + * MultipleFailureException. + */ + throw new org.junit.internal.runners.model.MultipleFailureException(errors); + } +} diff --git a/src/org/junit/runners/model/NoGenericTypeParametersValidator.java b/src/org/junit/runners/model/NoGenericTypeParametersValidator.java new file mode 100644 index 0000000..77662b8 --- /dev/null +++ b/src/org/junit/runners/model/NoGenericTypeParametersValidator.java @@ -0,0 +1,53 @@ +package org.junit.runners.model; + +import java.lang.reflect.GenericArrayType; +import java.lang.reflect.Method; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.lang.reflect.TypeVariable; +import java.lang.reflect.WildcardType; +import java.util.List; + +class NoGenericTypeParametersValidator { + private final Method fMethod; + + NoGenericTypeParametersValidator(Method method) { + this.fMethod = method; + } + + void validate(List<Throwable> errors) { + for (Type each : fMethod.getGenericParameterTypes()) + validateNoTypeParameterOnType(each, errors); + } + + private void validateNoTypeParameterOnType(Type type, List<Throwable> errors) { + if (type instanceof TypeVariable<?>) { + errors.add(new Exception("Method " + fMethod.getName() + + "() contains unresolved type variable " + type)); + } else if (type instanceof ParameterizedType) + validateNoTypeParameterOnParameterizedType((ParameterizedType) type, errors); + else if (type instanceof WildcardType) + validateNoTypeParameterOnWildcardType((WildcardType) type, errors); + else if (type instanceof GenericArrayType) + validateNoTypeParameterOnGenericArrayType((GenericArrayType) type, errors); + } + + private void validateNoTypeParameterOnParameterizedType(ParameterizedType parameterized, + List<Throwable> errors) { + for (Type each : parameterized.getActualTypeArguments()) + validateNoTypeParameterOnType(each, errors); + } + + private void validateNoTypeParameterOnWildcardType(WildcardType wildcard, + List<Throwable> errors) { + for (Type each : wildcard.getUpperBounds()) + validateNoTypeParameterOnType(each, errors); + for (Type each : wildcard.getLowerBounds()) + validateNoTypeParameterOnType(each, errors); + } + + private void validateNoTypeParameterOnGenericArrayType( + GenericArrayType arrayType, List<Throwable> errors) { + validateNoTypeParameterOnType(arrayType.getGenericComponentType(), errors); + } +}
\ No newline at end of file diff --git a/src/org/junit/runners/model/RunnerBuilder.java b/src/org/junit/runners/model/RunnerBuilder.java new file mode 100644 index 0000000..3a334be --- /dev/null +++ b/src/org/junit/runners/model/RunnerBuilder.java @@ -0,0 +1,104 @@ +package org.junit.runners.model; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.junit.internal.runners.ErrorReportingRunner; +import org.junit.runner.Runner; + +/** + * A RunnerBuilder is a strategy for constructing runners for classes. + * + * Only writers of custom runners should use <code>RunnerBuilder</code>s. A custom runner class with a constructor taking + * a <code>RunnerBuilder</code> parameter will be passed the instance of <code>RunnerBuilder</code> used to build that runner itself. + * For example, + * imagine a custom runner that builds suites based on a list of classes in a text file: + * + * <pre> + * \@RunWith(TextFileSuite.class) + * \@SuiteSpecFile("mysuite.txt") + * class MySuite {} + * </pre> + * + * The implementation of TextFileSuite might include: + * + * <pre> + * public TextFileSuite(Class testClass, RunnerBuilder builder) { + * // ... + * for (String className : readClassNames()) + * addRunner(builder.runnerForClass(Class.forName(className))); + * // ... + * } + * </pre> + * + * @see org.junit.runners.Suite + */ +public abstract class RunnerBuilder { + private final Set<Class<?>> parents= new HashSet<Class<?>>(); + + /** + * Override to calculate the correct runner for a test class at runtime. + * + * @param testClass class to be run + * @return a Runner + * @throws Throwable if a runner cannot be constructed + */ + public abstract Runner runnerForClass(Class<?> testClass) throws Throwable; + + /** + * Always returns a runner, even if it is just one that prints an error instead of running tests. + * @param testClass class to be run + * @return a Runner + */ + public Runner safeRunnerForClass(Class<?> testClass) { + try { + return runnerForClass(testClass); + } catch (Throwable e) { + return new ErrorReportingRunner(testClass, e); + } + } + + Class<?> addParent(Class<?> parent) throws InitializationError { + if (!parents.add(parent)) + throw new InitializationError(String.format("class '%s' (possibly indirectly) contains itself as a SuiteClass", parent.getName())); + return parent; + } + + void removeParent(Class<?> klass) { + parents.remove(klass); + } + + /** + * Constructs and returns a list of Runners, one for each child class in + * {@code children}. Care is taken to avoid infinite recursion: + * this builder will throw an exception if it is requested for another + * runner for {@code parent} before this call completes. + */ + public List<Runner> runners(Class<?> parent, Class<?>[] children) + throws InitializationError { + addParent(parent); + + try { + return runners(children); + } finally { + removeParent(parent); + } + } + + 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) { + Runner childRunner= safeRunnerForClass(each); + if (childRunner != null) + runners.add(childRunner); + } + return runners; + } +} diff --git a/src/org/junit/runners/model/RunnerScheduler.java b/src/org/junit/runners/model/RunnerScheduler.java new file mode 100644 index 0000000..fbc25a4 --- /dev/null +++ b/src/org/junit/runners/model/RunnerScheduler.java @@ -0,0 +1,21 @@ +package org.junit.runners.model; + +/** + * Represents a strategy for scheduling when individual test methods + * should be run (in serial or parallel) + * + * WARNING: still experimental, may go away. + */ +public interface RunnerScheduler { + /** + * Schedule a child statement to run + */ + void schedule(Runnable childStatement); + + /** + * Override to implement any behavior that must occur + * after all children have been scheduled (for example, + * waiting for them all to finish) + */ + void finished(); +} diff --git a/src/org/junit/runners/model/Statement.java b/src/org/junit/runners/model/Statement.java new file mode 100644 index 0000000..a7c5478 --- /dev/null +++ b/src/org/junit/runners/model/Statement.java @@ -0,0 +1,16 @@ +/** + * + */ +package org.junit.runners.model; + + +/** + * Represents one or more actions to be taken at runtime in the course + * of running a JUnit test suite. + */ +public abstract class Statement { + /** + * Run the action, throwing a {@code Throwable} if anything goes wrong. + */ + public abstract void evaluate() throws Throwable; +}
\ No newline at end of file diff --git a/src/org/junit/runners/model/TestClass.java b/src/org/junit/runners/model/TestClass.java new file mode 100644 index 0000000..362a13a --- /dev/null +++ b/src/org/junit/runners/model/TestClass.java @@ -0,0 +1,159 @@ +package org.junit.runners.model; + +import static java.lang.reflect.Modifier.isStatic; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.BeforeClass; + +/** + * Wraps a class to be run, providing method validation and annotation searching + */ +public class TestClass { + private final Class<?> fClass; + + private Map<Class<?>, List<FrameworkMethod>> fMethodsForAnnotations= new HashMap<Class<?>, List<FrameworkMethod>>(); + + private Map<Class<?>, List<FrameworkField>> fFieldsForAnnotations= new HashMap<Class<?>, List<FrameworkField>>(); + + /** + * Creates a {@code TestClass} wrapping {@code klass}. Each time this + * constructor executes, the class is scanned for annotations, which can be + * an expensive process (we hope in future JDK's it will not be.) Therefore, + * try to share instances of {@code TestClass} where possible. + */ + public TestClass(Class<?> klass) { + fClass= klass; + if (klass != null && klass.getConstructors().length > 1) + throw new IllegalArgumentException( + "Test class can only have one constructor"); + + for (Class<?> eachClass : getSuperClasses(fClass)) { + for (Method eachMethod : eachClass.getDeclaredMethods()) + addToAnnotationLists(new FrameworkMethod(eachMethod), + fMethodsForAnnotations); + for (Field eachField : eachClass.getDeclaredFields()) + addToAnnotationLists(new FrameworkField(eachField), + fFieldsForAnnotations); + } + } + + private <T extends FrameworkMember<T>> void addToAnnotationLists(T member, + Map<Class<?>, List<T>> map) { + for (Annotation each : member.getAnnotations()) { + Class<? extends Annotation> type= each.annotationType(); + List<T> members= getAnnotatedMembers(map, type); + if (member.isShadowedBy(members)) + return; + if (runsTopToBottom(type)) + members.add(0, member); + else + members.add(member); + } + } + + /** + * Returns, efficiently, all the non-overridden methods in this class and + * its superclasses that are annotated with {@code annotationClass}. + */ + public List<FrameworkMethod> getAnnotatedMethods( + Class<? extends Annotation> annotationClass) { + return getAnnotatedMembers(fMethodsForAnnotations, annotationClass); + } + + /** + * Returns, efficiently, all the non-overridden fields in this class and its + * superclasses that are annotated with {@code annotationClass}. + */ + public List<FrameworkField> getAnnotatedFields( + Class<? extends Annotation> annotationClass) { + return getAnnotatedMembers(fFieldsForAnnotations, annotationClass); + } + + private <T> List<T> getAnnotatedMembers(Map<Class<?>, List<T>> map, + Class<? extends Annotation> type) { + if (!map.containsKey(type)) + map.put(type, new ArrayList<T>()); + return map.get(type); + } + + private boolean runsTopToBottom(Class<? extends Annotation> annotation) { + return annotation.equals(Before.class) + || annotation.equals(BeforeClass.class); + } + + private List<Class<?>> getSuperClasses(Class<?> testClass) { + ArrayList<Class<?>> results= new ArrayList<Class<?>>(); + Class<?> current= testClass; + while (current != null) { + results.add(current); + current= current.getSuperclass(); + } + return results; + } + + /** + * Returns the underlying Java class. + */ + public Class<?> getJavaClass() { + return fClass; + } + + /** + * Returns the class's name. + */ + public String getName() { + if (fClass == null) + return "null"; + return fClass.getName(); + } + + /** + * Returns the only public constructor in the class, or throws an {@code + * AssertionError} if there are more or less than one. + */ + + public Constructor<?> getOnlyConstructor() { + Constructor<?>[] constructors= fClass.getConstructors(); + Assert.assertEquals(1, constructors.length); + return constructors[0]; + } + + /** + * Returns the annotations on this class + */ + public Annotation[] getAnnotations() { + if (fClass == null) + return new Annotation[0]; + return fClass.getAnnotations(); + } + + public <T> List<T> getAnnotatedFieldValues(Object test, + Class<? extends Annotation> annotationClass, Class<T> valueClass) { + List<T> results= new ArrayList<T>(); + for (FrameworkField each : getAnnotatedFields(annotationClass)) { + try { + Object fieldValue= each.get(test); + if (valueClass.isInstance(fieldValue)) + results.add(valueClass.cast(fieldValue)); + } catch (IllegalAccessException e) { + throw new RuntimeException( + "How did getFields return a field we couldn't access?", e); + } + } + return results; + } + + public boolean isANonStaticInnerClass() { + return fClass.isMemberClass() && !isStatic(fClass.getModifiers()); + } +} diff --git a/src/org/junit/runners/package-info.java b/src/org/junit/runners/package-info.java new file mode 100644 index 0000000..418acaf --- /dev/null +++ b/src/org/junit/runners/package-info.java @@ -0,0 +1,8 @@ +/** + * Provides standard {@link org.junit.runner.Runner Runner} implementations. + * + * @since 4.0 + * @see org.junit.runner.Runner + * @see org.junit.runners.BlockJUnit4ClassRunner + */ +package org.junit.runners;
\ No newline at end of file @@ -1 +1 @@ -3.8.2 +4.10 |