diff --git a/.classpath b/.classpath
index c98259118ee0..f648ca63c5ff 100644
--- a/.classpath
+++ b/.classpath
@@ -2,6 +2,8 @@
+
+
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 000000000000..43cf4920bd13
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,21 @@
+* text eol=lf
+*.gif binary
+*.GIF binary
+*.jar binary
+*.png binary
+*.jpg binary
+*.svg text eol=lf
+
+# These files do not have unix line endings. Do not normalize them for now.
+# Will fix these right before we cut JUnit 4.13.
+/src/main/java/org/junit/experimental/categories/CategoryFilterFactory.java -text
+/src/main/java/org/junit/experimental/categories/ExcludeCategories.java -text
+/src/main/java/org/junit/experimental/categories/IncludeCategories.java -text
+/src/main/java/org/junit/internal/Classes.java -text
+/src/main/java/org/junit/runner/FilterFactories.java -text
+/src/main/java/org/junit/runner/FilterFactory.java -text
+/src/main/java/org/junit/runner/FilterFactoryParams.java -text
+/src/main/java/org/junit/runner/JUnitCommandLineParseResult.java -text
+/src/test/java/org/junit/experimental/categories/CategoryFilterFactoryTest.java -text
+/src/test/java/org/junit/runner/FilterFactoriesTest.java -text
+/src/test/java/org/junit/runner/JUnitCommandLineParseResultTest.java -text
diff --git a/.gitignore b/.gitignore
index b3df647a6236..54f5aa75e33f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -10,4 +10,4 @@ MaxCore.max
*.iws
out
java.hprof.txt
-.gitattributes
\ No newline at end of file
+.DS_Store
diff --git a/.mvn/wrapper/maven-wrapper.jar b/.mvn/wrapper/maven-wrapper.jar
new file mode 100755
index 000000000000..41c70a7e0b7d
Binary files /dev/null and b/.mvn/wrapper/maven-wrapper.jar differ
diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties
new file mode 100755
index 000000000000..37cb61582de0
--- /dev/null
+++ b/.mvn/wrapper/maven-wrapper.properties
@@ -0,0 +1 @@
+distributionUrl=https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.1.1/apache-maven-3.1.1-bin.zip
\ No newline at end of file
diff --git a/.travis.settings.xml b/.travis.settings.xml
new file mode 100644
index 000000000000..332f39daa17b
--- /dev/null
+++ b/.travis.settings.xml
@@ -0,0 +1,22 @@
+
+
+
+ central
+ GCS Maven Central mirror
+ https://maven-central.storage-download.googleapis.com/maven2/
+ google-maven-central
+
+
+
+
+ junit-snapshot-repo
+ ${env.OSSRH_USERNAME}
+ ${env.OSSRH_PASSWORD}
+
+
+ junit-releases-repo
+ ${env.OSSRH_USERNAME}
+ ${env.OSSRH_PASSWORD}
+
+
+
diff --git a/.travis.yml b/.travis.yml
index 378d46143fe0..5b1744397f29 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,7 +1,69 @@
+dist: trusty
+
language: java
-script: mvn verify javadoc:javadoc site:site
-jdk:
- - oraclejdk7
- - oraclejdk8
- - openjdk7
- - openjdk6
+
+script: ./mvnw verify javadoc:javadoc site:site
+
+install:
+- ./mvnw --version
+
+stages:
+ - test
+ - name: deploy
+ if: (branch = main) AND (NOT type IN (pull_request))
+
+jobs:
+ include:
+ - name: Java 6
+ env: JDK=openjdk6
+ addons:
+ apt:
+ packages:
+ - openjdk-6-jdk
+ install:
+ # Download dependencies with JDK 8 because Mave Central supports
+ # TLS 1.2 only but OpenJDK 6 does not.
+ - export ORIGINAL_JAVA_HOME=$JAVA_HOME
+ - jdk_switcher use oraclejdk8
+ - ./mvnw test -DskipTests
+ # Delete all files created with JDK 8
+ - ./mvnw clean
+ # Restore desired JDK
+ - export JAVA_HOME=$ORIGINAL_JAVA_HOME
+ - jdk_switcher use openjdk6
+ - ./mvnw --version
+ - name: Java 7
+ jdk: openjdk7
+ - name: Java 8
+ jdk: oraclejdk8
+ - name: Java 9
+ jdk: oraclejdk9
+ - name: Java 10
+ jdk: openjdk10
+ - name: Java 11
+ jdk: openjdk11
+ - stage: deploy
+ name: "Publish snapshot artifacts"
+ addons:
+ apt:
+ packages:
+ - openjdk-6-jdk
+ install:
+ # Download dependencies with JDK 8 because Mave Central supports
+ # TLS 1.2 only but OpenJDK 6 does not.
+ - export ORIGINAL_JAVA_HOME=$JAVA_HOME
+ - jdk_switcher use oraclejdk8
+ - ./mvnw test -DskipTests
+ # Delete all files created with JDK 8
+ - ./mvnw clean
+ # Restore desired JDK
+ - export JAVA_HOME=$ORIGINAL_JAVA_HOME
+ - jdk_switcher use openjdk6
+ - ./mvnw --version
+ env:
+ - JDK=openjdk6
+ # OSSRH_USERNAME
+ - secure: griGZYDtqDMRUaYex/uAnpkWIQ/yodM6IOn4G8izWKpyGLeCxyXBG0FDcVo81xRq/9mMevj2idyW/xNP/HAQ45G4pyJUk/vTSMkNslzVjr7OBEtQQCN8XahSaOO0l0CJ5lzA6LdwWg7uDaf9znqZ0slt81u0S1NJmUZyYeUEim0=
+ # OSSRH_PASSWORD
+ - secure: EM7Z2M09HvLJXYJaeD/YmeF5A6tqavG2tBBeDcFZ7C6k0AI/wApe882pEMMoUG06xufKfSlt7WFJxoyU3M+fPOpeK5qZpJQxsHWnNJwbcbKzqMpM9mDsgIL9rtAvm9MuIIbIY2spiT0Cx3sHdh5qofaJHPL/u8Or5L9tE8FV1ew=
+ script: ./mvnw deploy --batch-mode --activate-profiles generate-docs --settings .travis.settings.xml
diff --git a/BUILDING b/BUILDING
index 5d1ec90fc5f1..496eeb03b820 100644
--- a/BUILDING
+++ b/BUILDING
@@ -1,8 +1,8 @@
BUILDING FROM GITHUB:
=====================
-git clone https://github.com/junit-team/junit.git
-cd junit
+git clone https://github.com/junit-team/junit4.git
+cd junit4
mvn install
BUILDING FROM JARS OR ZIPS:
@@ -11,4 +11,4 @@ BUILDING FROM JARS OR ZIPS:
The contents of the zip and jar files are largely maintained for historical
reasons. We do not at this time have an official way to build from the src
jar or zip. If this is an important missing feature, please let us know
-at http://github.com/junit-team/junit/issues
+at http://github.com/junit-team/junit4/issues
diff --git a/CODING_STYLE.txt b/CODING_STYLE.txt
index d9600afdf5a6..fefecdd26f83 100644
--- a/CODING_STYLE.txt
+++ b/CODING_STYLE.txt
@@ -1,3 +1,9 @@
-JUnit project uses the Google Java Style (http://google-styleguide.googlecode.com/svn/trunk/javaguide.html) for all new
-code (under org.junit.*). Legacy code (under junit.*) used the legacy guide specified in LEGACY_CODING_STYLE.txt in the
+JUnit project uses the Google Java Style (https://github.com/google/styleguide) for all new
+code (under org.junit.*). Deviating from this, indentation is 4 spaces instead of 2.
+
+Be sure to set text file encoding to UTF-8 and line delimiters to UNIX style.
+
+Since not all code has been formatted yet, please format only code lines you changed to avoid extraneous changes.
+
+Legacy code (under junit.*) used the legacy guide specified in LEGACY_CODING_STYLE.txt in the
project root.
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 629ea4cb6bbb..1943d567915c 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -1,16 +1,45 @@
+## Project License: Eclipse Public License v1.0
+
+- You will only Submit Contributions where You have authored 100% of the content.
+- You will only Submit Contributions to which You have the necessary rights. This means that if You are employed You have received the necessary permissions from Your employer to make the Contributions.
+- Whatever content You Contribute will be provided under the Project License(s).
+
+## Coding Conventions
+
+### Formatting
+
+See [CODING_STYLE.txt](CODING_STYLE.txt) for how we format our code.
+
+## How to submit a pull request
+
We love pull requests. Here is a quick guide:
1. You need to have Maven and a JDK (at least version 1.5) installed.
-2. Fork the repo (see https://help.github.com/articles/fork-a-repo)
-3. Create a new branch from master.
-4. Ensure that you have a clean state by running `mvn verify`
+2. [Fork the repo](https://help.github.com/articles/fork-a-repo).
+3. [Create a new branch](https://help.github.com/articles/creating-and-deleting-branches-within-your-repository/) from `main`.
+4. Ensure that you have a clean state by running `./mvnw verify`.
5. Add your change together with a test (tests are not needed for refactorings and documentation changes).
6. Format your code: Import the JUnit project in Eclipse and use its formatter or apply the rules in the `CODING_STYLE` file manually. Only format the code you've changed; reformatting unrelated code makes it harder for us to review your changes.
-6. Run `mvn verify` again and ensure all tests are passing.
-8. Push to your fork and submit a pull request.
+7. Run `./mvnw verify` again and ensure all tests are passing.
+8. Push to your fork and [submit a pull request](https://help.github.com/articles/creating-a-pull-request/).
Now you are waiting on us. We review your pull request and at least leave some comments.
Note that if you are thinking of providing a fix for one of the bugs or feature requests, it's usually
a good idea to add a comment to the bug to make sure that there's agreement on how we should proceed.
+
+## Limitations
+
+The JUnit team is not accepting changes to the code under the following paths:
+
+* `src/main/java/junit`
+* `test/java/junit/tests/framework`
+* `test/java/junit/tests/extensions`
+
+The reasoning is that the JUnit team feels that our users should focus on using either the JUnit4 or JUnit5 APIs.
+
+The team is also reluctant to accept changes that only update code from one code style to another.
+Generally the code in JUnit was approved by at least one person, so two people agreed that the style was reasonable.
+
+To find other places where you can have an impact, please see the [Issues tagged "up-for-grabs"](https://github.com/junit-team/junit4/issues?q=is%3Aissue+is%3Aopen+label%3Aup-for-grabs).
diff --git a/KEYS b/KEYS
new file mode 100644
index 000000000000..0433e72f3343
--- /dev/null
+++ b/KEYS
@@ -0,0 +1,70 @@
+This file contains the PGP key that is used to sign releases.
+
+Importing: `pgp < KEYS` or `gpg --import KEYS`
+
+Adding a key:
+`pgp -kxa >> KEYS`,
+or `(pgpk -ll && pgpk -xa ) >> KEYS`,
+or `(gpg --list-sigs && gpg --armor --export ) >> KEYS`
+
+================================
+
+pub rsa4096 2018-04-08 [SC]
+ FF6E2C001948C5F2F38B0CC385911F425EC61B51
+uid [ unknown] Open Source Development
+sig 3 85911F425EC61B51 2018-04-08 Open Source Development
+sub rsa4096 2018-04-08 [E]
+sig 85911F425EC61B51 2018-04-08 Open Source Development
+
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+
+mQINBFrKW9IBEACkqUvM7hU1WqOOeb1gZ7pUsRliHuoUvYIrd+hdp+qhPmJ0NG0W
+YhZK5UtJBmqvtHKRkbwYxUuya9zlBmCfQFf0GpFKJ65JSrPSkZADI3aZ4aUkxIUw
+nIRoUHucmr10Xftpebr/zaJk5oR8RdaL5FapapmcZmAaHR9CDWB8XtI318u314jq
+M5rKatnAZMERoPugOvvuAOz4bfZKwdfCmZKfYUM/TMSrSinXrGExSW6z4RhtqmpC
+E5M/7OoVfvDynVJKqNazqgigpmMNhOyzAhQsiKh1K0akyxTZbjeZKsdYfhCXvq0q
+k9+KM/cTllQ54MPnFWiObLkHeK0Waw8bI/vAJ4h4x/XM9iGYpkXv7F2/FVsHQdPe
+YJcwD/CkD8KHyiPaRKMeApiUtZsdAHU0L4X/lNmcooea/7ipskruUgwcm+RdLhRZ
+P949t1e7nqDZfpEHy90NiFxmlRAPSNqBLwefxY/hwBgog2jabDALJVcLCMosFWPj
+MQhFlGSIODiVcW8folGIjzkyNZbNMWkwnl2QnWp/h2TAwYQJOMqcv2MG9o5pyzpx
+97Iz1ngq1FlM/gJnGnNUydP2tAjT2L2U3MP1uX/EdRChdgPqdolqYhdFfwCr0Fpf
+W527bUZpReHCEiQ29ABSnQ711mO+d9+qM6edRyHUoBWz89IHt8sCunuvNwARAQAB
+tC1PcGVuIFNvdXJjZSBEZXZlbG9wbWVudCA8bWFpbEBtYXJjcGhpbGlwcC5kZT6J
+Ak4EEwEIADgWIQT/biwAGUjF8vOLDMOFkR9CXsYbUQUCWspb0gIbAwULCQgHAgYV
+CAkKCwIEFgIDAQIeAQIXgAAKCRCFkR9CXsYbUQyRD/9xm3BqdpWcRCE5UyB6nbwV
+8TgzMmbOhpFhhcjzobly/pKAbcofKsjhreENJkfBVUo+zAFx21ToC5tbH20wRtIE
+vQVCP6sAIzhYWU1ohafqVFP4+PztNBuYTnS6vGvSwzp0IXLIIoxSxo0IOED9uUS9
+DTxh1n9NnDLDe2pfjrXBblQtLSW3W5ISDoUvcoyO7Hk1OByW6MNsSoLvXIUNeVhB
+ju9TfYxFACJSWBhUxJfgip9Y2GrNBJaYGLZrTAoW1Lh1H1DfLV3wHDClQ1+H+oyx
+IOZULEGYY3MgZTd6Ner2yNAUCB7gVa50NiCZXCS74m+XzMrTEsdWjSMUaOe+dL0I
+9MCrgi4ycUHWIfTKx9gGlIOo3hSDMN+8Nj33XPjLT8kcfoFeUX8jTOvC1HFfTuQJ
+x2t/dKHizdrS3F6A/JQa7v8GNTrZFnEXkwgRTf3ccLoo3gPwzNJeCm2xNjvne1VH
+fvxzwNmq8M05oicEigvEed2VMStMhvT7dSiMAf66rEJHjjaHAoNqbLDEATYrWUP2
+I52txHSSxSJohxVP6Ec6dERnqqYi0mVyzBPo7mmFFBisq74w8RvZXyzvXE3BTiDL
+we1E/Z/AXbtJye9DickQ/G6RFtVLbUHQfzyRS/65JPtlH8rqJr58YWlylGImVLwE
+OsKNQrwLZ0UkfaWV7wqr3rkCDQRaylvSARAAnQG636wliEOLkXN662OZS6Qz2+cF
+ltCWboq9oX9FnA1PHnTY2cAtwS214RfWZxkjg6Stau+d1Wb8TsF/SUN3eKRSyrkA
+xlX0v552vj3xmmfNsslQX47e6aEWZ0du0M8jw7/f7Qxp0InkBfpQwjSg4ECoH4cA
+6dOFJIdxBv8dgS4K90HNuIHa+QYfVSVMjGwOjD9St6Pwkbg1sLedITRo59Bbv0J1
+4nE9LdWbCiwNrkDr24jTewdgrDaCpN6msUwcH1E0nYxuKAetHEi2OpgBhaY3RQ6Q
+PQB6NywvmD0xRllMqu4hSp70pHFtm8LvJdWOsJ5we3KijHuZzEbBVTTl+2DhNMI0
+KMoh+P/OmyNOfWD8DL4NO3pVv+mPDZn82/eZ3XY1/oSQrpyJaCBjRKasVTtfiA/F
+gYqTml6qZMjy6iywg84rLezELgcxHHvjhAKd4CfxyuCCgnGT0iRLFZKw44ZmOUqP
+DkyvGRddIyHag1K7UaM/2UMn6iPMy7XWcaFiH5Huhz43SiOdsWGuwNk4dDxHdxmz
+Sjps0H5dkfCciOFhEc54AFcGEXCWHXuxVqIq/hwqTmVl1RY+PTcQUIOfx36WW1ix
+JQf8TpVxUbooK8vr1jOFF6khorDXoZDJNhI2VKomWp8Y38EPGyiUPZNcnmSiezx+
+MoQwAbeqjFMKG7UAEQEAAYkCNgQYAQgAIBYhBP9uLAAZSMXy84sMw4WRH0JexhtR
+BQJaylvSAhsMAAoJEIWRH0JexhtR0LEP/RvYGlaokoosAYI5vNORAiYEc1Ow2McP
+I1ZafHhcVxZhlwF48dAC2bYcasDX/PbEdcD6pwo8ZU8eI8Ht0VpRQxeV/sP01m2Y
+EpAuyZ6jI7IQQCGcwQdN4qzQJxMAASl9JlplH2NniXV1/994FOtesT59ePMyexm5
+7lzhYXP1PGcdt8dH37r6z3XQu0lHRG/KBn7YhyA3zwJcno324KdBRJiynlc7uqQq
++ZptU9fR1+Nx0uoWZoFMsrQUmY34aAOPJu7jGMTG+VseMH6vDdNhhZs9JOlD/e/V
+aF7NyadjOUD4j/ud7c0z2EwqjDKMFTHGbIdawT/7jartT+9yGUO+EmScBMiMuJUT
+dCP4YDh3ExRdqefEBff3uE/rAP73ndNYdIVq9U0gY0uSNCD9JPfj4aCN52y9a2pS
+7Dg7KB/Z8SH1R9IWP+t0HvVtAILdsLExNFTedJGHRh7uaC7pwRz01iivmtAKYICz
+ruqlJie/IdEFFK/sus6fZek29odTrQxx42HGHO5GCNyEdK9jKVAeuZ10vcaNbuBp
+iP7sf8/BsiEU4wHE8gjFeUPRiSjnERgXQwfJosLgf/K/SShQn2dCkYZRNF+SWJ6Z
+2tQxcW5rpUjtclV/bRVkUX21EYfwA6SMB811mI7AVy8WPXCe8La72ukmaxEGbpJ8
+mdzS2PJko7mm
+=l0XA
+-----END PGP PUBLIC KEY BLOCK-----
diff --git a/README.md b/README.md
index 660111c796e8..f73f89ca5ed0 100644
--- a/README.md
+++ b/README.md
@@ -2,11 +2,8 @@
JUnit is a simple framework to write repeatable tests. It is an instance of the xUnit architecture for unit testing frameworks.
For more information, please visit:
-* [Wiki](https://github.com/junit-team/junit/wiki)
-* [Download and Install guide](https://github.com/junit-team/junit/wiki/Download-and-Install)
-* [Getting Started](https://github.com/junit-team/junit/wiki/Getting-started)
-
-[![Latest Build Status](https://junit.ci.cloudbees.com/job/JUnit/badge/icon)](https://junit.ci.cloudbees.com/)
-
-[![Built on DEV@cloud](http://www.cloudbees.com/sites/default/files/Button-Built-on-CB-1.png)](http://www.cloudbees.com/foss/foss-dev.cb)
+* [Wiki](https://github.com/junit-team/junit4/wiki)
+* [Download and Install guide](https://github.com/junit-team/junit4/wiki/Download-and-Install)
+* [Getting Started](https://github.com/junit-team/junit4/wiki/Getting-started)
+[![Build Status](https://travis-ci.org/junit-team/junit4.svg?branch=main)](https://travis-ci.org/junit-team/junit4)
diff --git a/acknowledgements.txt b/acknowledgements.txt
index fa1aa8596737..bbc24dfe041f 100644
--- a/acknowledgements.txt
+++ b/acknowledgements.txt
@@ -82,7 +82,7 @@
reinholdfuereder@github For initial test for GH-39
2011 Apr 15
- ububenheimer@github for bug report https://github.com/junit-team/junit/issues/208
+ ububenheimer@github for bug report https://github.com/junit-team/junit4/issues/208
2011 Apr 29
reinholdfuereder@github: bug report, test, and fix for GH-38:
@@ -160,4 +160,4 @@
== NOTE: as of September 2011, we have stopped recording contributions here.
For a full list of everyone who has contributed great bug reports and code, please see
- http://github.com/junit-team/junit
+ http://github.com/junit-team/junit4
diff --git a/doc/ReleaseNotes4.10.html b/doc/ReleaseNotes4.10.html
index 84bc3d47cec2..b6b9483b8a74 100644
--- a/doc/ReleaseNotes4.10.html
+++ b/doc/ReleaseNotes4.10.html
@@ -1,6 +1,6 @@
-
Summary of Changes in version 4.10 [unreleased!]
+
Summary of Changes in version 4.10
-
A full summary of commits between 4.9 and 4.10 is on github
+
A full summary of commits between 4.9 and 4.10 is on github
junit-dep has correct contents
diff --git a/doc/ReleaseNotes4.10.md b/doc/ReleaseNotes4.10.md
index 0b0a9d36a83a..5f19bf744889 100644
--- a/doc/ReleaseNotes4.10.md
+++ b/doc/ReleaseNotes4.10.md
@@ -2,7 +2,7 @@
Thanks to a full cast of contributors of bug fixes and new features.
-A full summary of commits between 4.9 and 4.10 is on [github](https://github.com/junit-team/junit/compare/r4.9...4.10)
+A full summary of commits between 4.9 and 4.10 is on [github](https://github.com/junit-team/junit4/compare/r4.9...r4.10)
### junit-dep has correct contents ###
diff --git a/doc/ReleaseNotes4.12.md b/doc/ReleaseNotes4.12.md
index db5197f02f80..744ecf369062 100644
--- a/doc/ReleaseNotes4.12.md
+++ b/doc/ReleaseNotes4.12.md
@@ -2,23 +2,23 @@
# Assertions
-### [Pull request #611:](https://github.com/junit-team/junit/pull/611) Assert.assertNotEquals() for `float` parameters
+### [Pull request #611:](https://github.com/junit-team/junit4/pull/611) Assert.assertNotEquals() for `float` parameters
Version 4.11 added `Assert.assertEquals()` for `float` parameters with a delta, and `Assert.assertNotEquals()`. This is the combination of those two features.
-### [Pull request #632:](https://github.com/junit-team/junit/pull/632) Assert.assertArrayEquals() for `boolean[]` parameters.
+### [Pull request #632:](https://github.com/junit-team/junit4/pull/632) Assert.assertArrayEquals() for `boolean[]` parameters.
`Assert.assertArrayEquals()` previously existed for all primitive array types, except `boolean[]`. This has now been added for `boolean[]`.
-### [Pull request #918:](https://github.com/junit-team/junit/pull/918) Avoid potentially expensive reflection-based loop in Assert.assertArrayEquals()
+### [Pull request #918:](https://github.com/junit-team/junit4/pull/918) Avoid potentially expensive reflection-based loop in Assert.assertArrayEquals()
In the usual case, where the array elements are in fact exactly equal, the potentially expensive reflection-based loop to compare them is avoided by using `Arrays.deepEquals()` first. The exact comparison is only executed when `deepEquals()` returns `false`.
# Command-line options
-### [Pull request #647:](https://github.com/junit-team/junit/pull/647) Support command-line `--filter` param.
+### [Pull request #647:](https://github.com/junit-team/junit4/pull/647) Support command-line `--filter` param.
When running JUnit from the command line, a command-line parameter can be supplied using `--filter`, which supplies a filter that will restrict which tests and subtests from the rest of the command will be run. For example, this will run only the tests in ExampleTestSuite that are in categories Cat1 or Cat2:
@@ -33,12 +33,12 @@ In general, the argument to `--filter` should be `ClassName=param`, where `Class
# Test Runners
-### [Pull request #763:](https://github.com/junit-team/junit/pull/763) Allow custom test runners to create their own TestClasses and customize the scanning of annotations.
+### [Pull request #763:](https://github.com/junit-team/junit4/pull/763) Allow custom test runners to create their own TestClasses and customize the scanning of annotations.
This introduces some extension points to `ParentRunner` to allow subclasses to control creation
of the `TestClass` instance and to scan for annotations.
-### [Pull request #817:](https://github.com/junit-team/junit/pull/817) Support for context hierarchies
+### [Pull request #817:](https://github.com/junit-team/junit4/pull/817) Support for context hierarchies
The `AnnotatedBuilder` is a strategy for constructing runners for test classes that have been annotated with the `@RunWith` annotation. All tests within such a class will be executed using the runner that was specified within the annotation.
@@ -95,17 +95,17 @@ The key points to note here are:
One example of a runner that makes use of this extension is the Hierarchical Context Runner (see https://github.com/bechte/junit-hierarchicalcontextrunner/wiki).
-### [Pull request #716:](https://github.com/junit-team/junit/pull/716) Fix annotation collection from superclasses of JUnit3 tests.
+### [Pull request #716:](https://github.com/junit-team/junit4/pull/716) Fix annotation collection from superclasses of JUnit3 tests.
Previously `Description.getAnnotations()` would always return an empty list for _test*_ methods derived from superclasses.
-### [Pull request #625 (commit 72af03c49f):](https://github.com/junit-team/junit/commit/72af03c49fdad5f10e36c7eb4e7045feb971d253) Make `RunNotifier` code concurrent.
+### [Pull request #625 (commit 72af03c49f):](https://github.com/junit-team/junit4/commit/72af03c49fdad5f10e36c7eb4e7045feb971d253) Make `RunNotifier` code concurrent.
When running tests from multiple threads, JUnit will now call `RunListener` methods from multiple threads if the listener class is annotated with `@RunListener.ThreadSafe`. In addition, the code in `RunNotifier` has been modified to not use locks.
-### [Pull request #684:](https://github.com/junit-team/junit/pull/684) Adding `AnnotationValidator` framework and validation checks for `@Category`.
+### [Pull request #684:](https://github.com/junit-team/junit4/pull/684) Adding `AnnotationValidator` framework and validation checks for `@Category`.
This allows for validation to be added to annotations. Validators should extend `AnnotationValidator` and be attached to annotations with the `@ValidateWith` annotation. `CategoryValidator` extends `AnnotationValidator` and ensures that incompatible annotations (`@BeforeClass`, `@AfterClass`, `@Before`, `@After`) are not used in conjunction with `@Category`.
@@ -113,12 +113,12 @@ This allows for validation to be added to annotations. Validators should extend
# Exception Testing
-### [Pull request #583:](https://github.com/junit-team/junit/pull/583) [Pull request #720:](https://github.com/junit-team/junit/pull/720) Fix handling of `AssertionError` and `AssumptionViolatedException` in `ExpectedException` rule.
+### [Pull request #583:](https://github.com/junit-team/junit4/pull/583) [Pull request #720:](https://github.com/junit-team/junit4/pull/720) Fix handling of `AssertionError` and `AssumptionViolatedException` in `ExpectedException` rule.
`ExpectedException` didn't handle `AssertionError`s and `AssumptionViolatedException` well. This has been fixed. The new documentation explains the usage of `ExpectedException` for testing these exceptions. The two methods `handleAssertionErrors()` and `handleAssumptionViolatedExceptions()` are not needed anymore. If you have used them, just remove it and read `ExpectedException`'s documentation.
-### [Pull request #818:](https://github.com/junit-team/junit/pull/818) [Pull request #993:](https://github.com/junit-team/junit/pull/993) External version of AssumptionViolatedException
+### [Pull request #818:](https://github.com/junit-team/junit4/pull/818) [Pull request #993:](https://github.com/junit-team/junit4/pull/993) External version of AssumptionViolatedException
In JUnit 4.11 and earlier, if you wanted to write a custom runner that handled
`AssumptionViolatedException` or you needed to create an instance of `AssumptionViolatedException`
@@ -133,7 +133,7 @@ simpler than the ones in the internal version. That being said,
it's recommended that you create `AssumptionViolatedException` via the methods in `Assume`.
-### [Pull request #985:](https://github.com/junit-team/junit/pull/985) Change AssumptionViolatedException to not set the cause to null; fixes issue #494
+### [Pull request #985:](https://github.com/junit-team/junit4/pull/985) Change AssumptionViolatedException to not set the cause to null; fixes issue #494
Previously, the `AssumptionViolatedException` constructors would explicitly set the cause to `null`
(unless you use a constructor where you provide a `Throwable`, in which case it would set that as
@@ -144,7 +144,7 @@ With this change, the cause is only set if you pass in a `Throwable`.
It's recommended that you create `AssumptionViolatedException` via the methods in `Assume`.
-### [Pull request #542:](https://github.com/junit-team/junit/pull/542) Customized failure message for `ExpectedException`
+### [Pull request #542:](https://github.com/junit-team/junit4/pull/542) Customized failure message for `ExpectedException`
`ExpectedException` now allows customization of the failure message when the test does not throw the expected exception. For example:
@@ -155,15 +155,22 @@ thrown.reportMissingExceptionWithMessage("FAIL: Expected exception to be thrown"
If a custom failure message is not provided, a default message is used.
+### [Pull request #1013:](https://github.com/junit-team/junit4/pull/1013) Make ErrorCollector#checkSucceeds generic
+
+The method `ErrorCollector.checkSucceeds()` is now generic. Previously, you could only pass
+in a `Callable
*
- * @see Theories
+ * @see Theories
*
* @since 4.4
*/
public class Assume {
+
+ /**
+ * Do not instantiate.
+ * @deprecated since 4.13.
+ */
+ @Deprecated
+ public Assume() {
+ }
+
/**
* If called with an expression evaluating to {@code false}, the test will halt and be ignored.
*/
@@ -45,7 +54,7 @@ public static void assumeTrue(boolean b) {
* The inverse of {@link #assumeTrue(boolean)}.
*/
public static void assumeFalse(boolean b) {
- assumeTrue(!b);
+ assumeThat(b, is(false));
}
/**
@@ -67,9 +76,11 @@ public static void assumeFalse(String message, boolean b) {
}
/**
- * If called with one or more null elements in objects, the test will halt and be ignored.
+ * If called with a {@code null} array or one or more {@code null} elements in {@code objects},
+ * the test will halt and be ignored.
*/
public static void assumeNotNull(Object... objects) {
+ assumeThat(objects, notNullValue());
assumeThat(asList(objects), everyItem(notNullValue()));
}
diff --git a/src/main/java/org/junit/AssumptionViolatedException.java b/src/main/java/org/junit/AssumptionViolatedException.java
index e48ddf000a78..1d62190a2315 100644
--- a/src/main/java/org/junit/AssumptionViolatedException.java
+++ b/src/main/java/org/junit/AssumptionViolatedException.java
@@ -40,7 +40,7 @@ public AssumptionViolatedException(String message) {
/**
* An assumption exception with the given message and a cause.
*/
- public AssumptionViolatedException(String assumption, Throwable t) {
- super(assumption, t);
+ public AssumptionViolatedException(String message, Throwable t) {
+ super(message, t);
}
}
diff --git a/src/main/java/org/junit/ClassRule.java b/src/main/java/org/junit/ClassRule.java
index 02c40a713fce..94ee29f9f51d 100644
--- a/src/main/java/org/junit/ClassRule.java
+++ b/src/main/java/org/junit/ClassRule.java
@@ -28,7 +28,10 @@
* 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. However, Rules defined by fields will always be applied
- * before Rules defined by methods.
+ * after Rules defined by methods, i.e. the Statements returned by the former will
+ * be executed around those returned by the latter.
+ *
+ *
Usage
*
* 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:
@@ -79,9 +82,37 @@
*
* For more information and more examples, see {@link org.junit.rules.TestRule}.
*
+ *
Ordering
+ *
+ * You can use {@link #order()} if you want to have control over the order in
+ * which the Rules are applied.
+ *
+ *
+ * public class ThreeClassRules {
+ * @ClassRule(order = 0)
+ * public static LoggingRule outer = new LoggingRule("outer rule");
+ *
+ * @ClassRule(order = 1)
+ * public static LoggingRule middle = new LoggingRule("middle rule");
+ *
+ * @ClassRule(order = 2)
+ * public static LoggingRule inner = new LoggingRule("inner rule");
+ *
+ * // ...
+ * }
+ *
+ *
* @since 4.9
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.METHOD})
public @interface ClassRule {
+
+ /**
+ * Specifies the order in which rules are applied. The rules with a higher value are inner.
+ *
+ * @since 4.13
+ */
+ int order() default Rule.DEFAULT_ORDER;
+
}
diff --git a/src/main/java/org/junit/ComparisonFailure.java b/src/main/java/org/junit/ComparisonFailure.java
index 9563e61c5c7a..d1daa863c3f3 100644
--- a/src/main/java/org/junit/ComparisonFailure.java
+++ b/src/main/java/org/junit/ComparisonFailure.java
@@ -21,7 +21,7 @@ public class ComparisonFailure extends AssertionError {
/*
* We have to use the f prefix until the next major release to ensure
* serialization compatibility.
- * See https://github.com/junit-team/junit/issues/976
+ * See https://github.com/junit-team/junit4/issues/976
*/
private String fExpected;
private String fActual;
diff --git a/src/main/java/org/junit/Rule.java b/src/main/java/org/junit/Rule.java
index 711235c5a7f3..9370e94f51fc 100644
--- a/src/main/java/org/junit/Rule.java
+++ b/src/main/java/org/junit/Rule.java
@@ -16,12 +16,14 @@
* 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 order of fields first, then methods.
+ * annotated {@link Rule}s on a class, they will be applied in order of methods first, then fields.
* However, if there are multiple fields (or methods) they will be applied in an order
* that depends on your JVM's implementation of the reflection API, which is
* undefined, in general. Rules defined by fields will always be applied
- * before Rules defined by methods. You can use a {@link org.junit.rules.RuleChain} if you want
- * to have control over the order in which the Rules are applied.
+ * after Rules defined by methods, i.e. the Statements returned by the former will
+ * be executed around those returned by the latter.
+ *
+ *
Usage
*
* For example, here is a test class that creates a temporary folder before
* each test method, and deletes it after each:
@@ -61,10 +63,39 @@
* For more information and more examples, see
* {@link org.junit.rules.TestRule}.
*
+ *
Ordering
+ *
+ * You can use {@link #order()} if you want to have control over the order in
+ * which the Rules are applied.
+ *
+ *
+ * public class ThreeRules {
+ * @Rule(order = 0)
+ * public LoggingRule outer = new LoggingRule("outer rule");
+ *
+ * @Rule(order = 1)
+ * public LoggingRule middle = new LoggingRule("middle rule");
+ *
+ * @Rule(order = 2)
+ * public LoggingRule inner = new LoggingRule("inner rule");
+ *
+ * // ...
+ * }
+ *
+ *
* @since 4.7
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.METHOD})
public @interface Rule {
-}
\ No newline at end of file
+ int DEFAULT_ORDER = -1;
+
+ /**
+ * Specifies the order in which rules are applied. The rules with a higher value are inner.
+ *
+ * @since 4.13
+ */
+ int order() default DEFAULT_ORDER;
+
+}
diff --git a/src/main/java/org/junit/Test.java b/src/main/java/org/junit/Test.java
index 71ac4287dbef..1db6fc7ab073 100644
--- a/src/main/java/org/junit/Test.java
+++ b/src/main/java/org/junit/Test.java
@@ -1,5 +1,7 @@
package org.junit;
+import org.junit.function.ThrowingRunnable;
+
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -23,24 +25,40 @@
* }
*
*
- * The Test annotation supports two optional parameters.
- * The first, expected, declares that a test method should throw
+ * The Test annotation supports two optional parameters for
+ * exception testing and for limiting test execution time.
+ *
+ *
Exception Testing
+ *
+ * The parameter expected 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:
*
- * @Test(expected=IndexOutOfBoundsException.class) public void outOfBounds() {
+ * @Test(expected=IndexOutOfBoundsException.class)
+ * public void outOfBounds() {
* new ArrayList<Object>().get(1);
* }
*
- * If the exception's message or one of its properties should be verified, the
- * {@link org.junit.rules.ExpectedException ExpectedException} rule can be used. Further
+ *
+ * Using the parameter expected for exception testing comes with
+ * some limitations: only the exception's type can be checked and it is not
+ * possible to precisely specify the code that throws the exception. Therefore
+ * JUnit 4 has improved its support for exception testing with
+ * {@link Assert#assertThrows(Class, ThrowingRunnable)} and the
+ * {@link org.junit.rules.ExpectedException ExpectedException} rule.
+ * With assertThrows the code that throws the exception can be
+ * precisely specified. If the exception's message or one of its properties
+ * should be verified, the ExpectedException rule can be used. Further
* information about exception testing can be found at the
- * JUnit Wiki.
+ * JUnit Wiki.
+ *
+ *
Timeout
*
- * The second optional parameter, timeout, causes a test to fail if it takes
+ * The parameter timeout causes a test to fail if it takes
* longer than a specified amount of clock time (measured in milliseconds). The following test fails:
*
@@ -66,7 +85,7 @@
public @interface Test {
/**
- * Default empty exception
+ * Default empty exception.
*/
static class None extends Throwable {
private static final long serialVersionUID = 1L;
diff --git a/src/main/java/org/junit/TestCouldNotBeSkippedException.java b/src/main/java/org/junit/TestCouldNotBeSkippedException.java
new file mode 100644
index 000000000000..4804493290ef
--- /dev/null
+++ b/src/main/java/org/junit/TestCouldNotBeSkippedException.java
@@ -0,0 +1,19 @@
+package org.junit;
+
+/**
+ * Indicates that a test that indicated that it should be skipped could not be skipped.
+ * This can be thrown if a test uses the methods in {@link Assume} to indicate that
+ * it should be skipped, but before processing of the test was completed, other failures
+ * occured.
+ *
+ * @see org.junit.Assume
+ * @since 4.13
+ */
+public class TestCouldNotBeSkippedException extends RuntimeException {
+ private static final long serialVersionUID = 1L;
+
+ /** Creates an instance using the given assumption failure. */
+ public TestCouldNotBeSkippedException(org.junit.internal.AssumptionViolatedException cause) {
+ super("Test could not be skipped due to other failures", cause);
+ }
+}
diff --git a/src/main/java/org/junit/experimental/categories/Categories.java b/src/main/java/org/junit/experimental/categories/Categories.java
index 290c1800cbc6..0c73ed82afff 100644
--- a/src/main/java/org/junit/experimental/categories/Categories.java
+++ b/src/main/java/org/junit/experimental/categories/Categories.java
@@ -2,8 +2,10 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
+import java.util.LinkedHashSet;
import java.util.Set;
import org.junit.runner.Description;
@@ -76,7 +78,7 @@
*
*
* @version 4.12
- * @see Categories at JUnit wiki
+ * @see Categories at JUnit wiki
*/
public class Categories extends Suite {
@@ -86,13 +88,13 @@ public class Categories extends Suite {
* Determines the tests to run that are annotated with categories specified in
* the value of this annotation or their subtypes unless excluded with {@link ExcludeCategory}.
*/
- public Class>[] value() default {};
+ Class>[] value() default {};
/**
* If true, runs tests annotated with any of the categories in
* {@link IncludeCategory#value()}. Otherwise, runs tests only if annotated with all of the categories.
*/
- public boolean matchAny() default true;
+ boolean matchAny() default true;
}
@Retention(RetentionPolicy.RUNTIME)
@@ -101,13 +103,13 @@ public class Categories extends Suite {
* Determines the tests which do not run if they are annotated with categories specified in the
* value of this annotation or their subtypes regardless of being included in {@link IncludeCategory#value()}.
*/
- public Class>[] value() default {};
+ Class>[] value() default {};
/**
* If true, the tests annotated with any of the categories in {@link ExcludeCategory#value()}
* do not run. Otherwise, the tests do not run if and only if annotated with all categories.
*/
- public boolean matchAny() default true;
+ boolean matchAny() default true;
}
public static class CategoryFilter extends Filter {
@@ -117,10 +119,7 @@ public static class CategoryFilter extends Filter {
private final boolean excludedAny;
public static CategoryFilter include(boolean matchAny, Class>... categories) {
- if (hasNull(categories)) {
- throw new NullPointerException("has null category");
- }
- return categoryFilter(matchAny, createSet(categories), true, null);
+ return new CategoryFilter(matchAny, categories, true, null);
}
public static CategoryFilter include(Class> category) {
@@ -132,10 +131,7 @@ public static CategoryFilter include(Class>... categories) {
}
public static CategoryFilter exclude(boolean matchAny, Class>... categories) {
- if (hasNull(categories)) {
- throw new NullPointerException("has null category");
- }
- return categoryFilter(true, null, matchAny, createSet(categories));
+ return new CategoryFilter(true, null, matchAny, categories);
}
public static CategoryFilter exclude(Class> category) {
@@ -151,14 +147,30 @@ public static CategoryFilter categoryFilter(boolean matchAnyInclusions, Set includedCategory, Class> excludedCategory) {
+ includedAny = true;
+ excludedAny = true;
+ included = nullableClassToSet(includedCategory);
+ excluded = nullableClassToSet(excludedCategory);
+ }
+
protected CategoryFilter(boolean matchAnyIncludes, Set> includes,
- boolean matchAnyExcludes, Set> excludes) {
+ boolean matchAnyExcludes, Set> excludes) {
includedAny = matchAnyIncludes;
excludedAny = matchAnyExcludes;
included = copyAndRefine(includes);
excluded = copyAndRefine(excludes);
}
+ private CategoryFilter(boolean matchAnyIncludes, Class>[] inclusions,
+ boolean matchAnyExcludes, Class>[] exclusions) {
+ includedAny = matchAnyIncludes;
+ excludedAny = matchAnyExcludes;
+ included = createSet(inclusions);
+ excluded = createSet(exclusions);
+ }
+
/**
* @see #toString()
*/
@@ -284,23 +296,13 @@ private static Class>[] directCategories(Description description) {
}
private static Set> copyAndRefine(Set> classes) {
- HashSet> c= new HashSet>();
+ Set> c= new LinkedHashSet>();
if (classes != null) {
c.addAll(classes);
}
c.remove(null);
return c;
}
-
- private static boolean hasNull(Class>... classes) {
- if (classes == null) return false;
- for (Class> clazz : classes) {
- if (clazz == null) {
- return true;
- }
- }
- return false;
- }
}
public Categories(Class> klass, RunnerBuilder builder) throws InitializationError {
@@ -315,7 +317,6 @@ public Categories(Class> klass, RunnerBuilder builder) throws InitializationEr
} catch (NoTestsRemainException e) {
throw new InitializationError(e);
}
- assertNoCategorizedDescendentsOfUncategorizeableParents(getDescription());
}
private static Set> getIncludedCategory(Class> klass) {
@@ -338,34 +339,6 @@ private static boolean isAnyExcluded(Class> klass) {
return annotation == null || annotation.matchAny();
}
- private static void assertNoCategorizedDescendentsOfUncategorizeableParents(Description description) throws InitializationError {
- if (!canHaveCategorizedChildren(description)) {
- assertNoDescendantsHaveCategoryAnnotations(description);
- }
- for (Description each : description.getChildren()) {
- assertNoCategorizedDescendentsOfUncategorizeableParents(each);
- }
- }
-
- private static 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;
- }
-
private static boolean hasAssignableTo(Set> assigns, Class> to) {
for (final Class> from : assigns) {
if (to.isAssignableFrom(from)) {
@@ -375,11 +348,28 @@ private static boolean hasAssignableTo(Set> assigns, Class> to) {
return false;
}
- private static Set> createSet(Class>... t) {
- final Set> set= new HashSet>();
- if (t != null) {
- Collections.addAll(set, t);
+ private static Set> createSet(Class>[] classes) {
+ // Not throwing a NPE if t is null is a bad idea, but it's the behavior from JUnit 4.12
+ // for include(boolean, Class>...) and exclude(boolean, Class>...)
+ if (classes == null || classes.length == 0) {
+ return Collections.emptySet();
+ }
+ for (Class> category : classes) {
+ if (category == null) {
+ throw new NullPointerException("has null category");
+ }
}
- return set;
+
+ return classes.length == 1
+ ? Collections.>singleton(classes[0])
+ : new LinkedHashSet>(Arrays.asList(classes));
+ }
+
+ private static Set> nullableClassToSet(Class> nullableClass) {
+ // Not throwing a NPE if t is null is a bad idea, but it's the behavior from JUnit 4.11
+ // for CategoryFilter(Class> includedCategory, Class> excludedCategory)
+ return nullableClass == null
+ ? Collections.>emptySet()
+ : Collections.>singleton(nullableClass);
}
}
diff --git a/src/main/java/org/junit/experimental/categories/CategoryFilterFactory.java b/src/main/java/org/junit/experimental/categories/CategoryFilterFactory.java
index cee1ae724595..e9bdab77e502 100644
--- a/src/main/java/org/junit/experimental/categories/CategoryFilterFactory.java
+++ b/src/main/java/org/junit/experimental/categories/CategoryFilterFactory.java
@@ -37,7 +37,11 @@ private List> parseCategories(String categories) throws ClassNotFoundEx
List> categoryClasses = new ArrayList>();
for (String category : categories.split(",")) {
- Class> categoryClass = Classes.getClass(category);
+ /*
+ * Load the category class using the context class loader.
+ * If there is no context class loader, use the class loader for this class.
+ */
+ Class> categoryClass = Classes.getClass(category, getClass());
categoryClasses.add(categoryClass);
}
diff --git a/src/main/java/org/junit/experimental/max/MaxHistory.java b/src/main/java/org/junit/experimental/max/MaxHistory.java
index 45a40336b3f6..ab7443f38535 100644
--- a/src/main/java/org/junit/experimental/max/MaxHistory.java
+++ b/src/main/java/org/junit/experimental/max/MaxHistory.java
@@ -64,7 +64,7 @@ private static MaxHistory readHistory(File storedResults)
/*
* We have to use the f prefix until the next major release to ensure
* serialization compatibility.
- * See https://github.com/junit-team/junit/issues/976
+ * See https://github.com/junit-team/junit4/issues/976
*/
private final Map fDurations = new HashMap();
private final Map fFailureTimestamps = new HashMap();
@@ -75,10 +75,15 @@ private MaxHistory(File storedResults) {
}
private void save() throws IOException {
- ObjectOutputStream stream = new ObjectOutputStream(new FileOutputStream(
- fHistoryStore));
- stream.writeObject(this);
- stream.close();
+ ObjectOutputStream stream = null;
+ try {
+ stream = new ObjectOutputStream(new FileOutputStream(fHistoryStore));
+ stream.writeObject(this);
+ } finally {
+ if (stream != null) {
+ stream.close();
+ }
+ }
}
Long getFailureTimestamp(Description key) {
diff --git a/src/main/java/org/junit/experimental/results/PrintableResult.java b/src/main/java/org/junit/experimental/results/PrintableResult.java
index ffe22f0e957c..0f67766f9141 100644
--- a/src/main/java/org/junit/experimental/results/PrintableResult.java
+++ b/src/main/java/org/junit/experimental/results/PrintableResult.java
@@ -54,6 +54,15 @@ public int failureCount() {
return result.getFailures().size();
}
+ /**
+ * Returns the failures in this result.
+ *
+ * @since 4.13
+ */
+ public List failures() {
+ return result.getFailures();
+ }
+
@Override
public String toString() {
ByteArrayOutputStream stream = new ByteArrayOutputStream();
diff --git a/src/main/java/org/junit/experimental/results/ResultMatchers.java b/src/main/java/org/junit/experimental/results/ResultMatchers.java
index cf58f1bc499a..e111093150c1 100644
--- a/src/main/java/org/junit/experimental/results/ResultMatchers.java
+++ b/src/main/java/org/junit/experimental/results/ResultMatchers.java
@@ -4,6 +4,7 @@
import org.hamcrest.Description;
import org.hamcrest.Matcher;
import org.hamcrest.TypeSafeMatcher;
+import org.junit.runner.notification.Failure;
/**
* Matchers on a PrintableResult, to enable JUnit self-tests.
@@ -14,6 +15,15 @@
*
*/
public class ResultMatchers {
+
+ /**
+ * Do not instantiate.
+ * @deprecated will be private soon.
+ */
+ @Deprecated
+ public ResultMatchers() {
+ }
+
/**
* Matches if the tests are all successful
*/
@@ -52,14 +62,34 @@ public void describeTo(Description description) {
};
}
+ /**
+ * Matches if the result has exactly one failure matching the given matcher.
+ *
+ * @since 4.13
+ */
+ public static Matcher hasSingleFailureMatching(final Matcher matcher) {
+ return new TypeSafeMatcher() {
+ @Override
+ public boolean matchesSafely(PrintableResult item) {
+ return item.failureCount() == 1 && matcher.matches(item.failures().get(0).getException());
+ }
+
+ public void describeTo(Description description) {
+ description.appendText("has failure with exception matching ");
+ matcher.describeTo(description);
+ }
+ };
+ }
+
/**
* Matches if the result has one or more failures, and at least one of them
* contains {@code string}
*/
public static Matcher hasFailureContaining(final String string) {
- return new BaseMatcher() {
- public boolean matches(Object item) {
- return item.toString().contains(string);
+ return new TypeSafeMatcher() {
+ @Override
+ public boolean matchesSafely(PrintableResult item) {
+ return item.failureCount() > 0 && item.toString().contains(string);
}
public void describeTo(Description description) {
diff --git a/src/main/java/org/junit/experimental/theories/ParametersSuppliedBy.java b/src/main/java/org/junit/experimental/theories/ParametersSuppliedBy.java
index 15b5d9518d8a..846a39e46339 100644
--- a/src/main/java/org/junit/experimental/theories/ParametersSuppliedBy.java
+++ b/src/main/java/org/junit/experimental/theories/ParametersSuppliedBy.java
@@ -17,7 +17,7 @@
*
* In addition, annotations themselves can be annotated with
* @ParametersSuppliedBy, and then used similarly. ParameterSuppliedBy
- * annotations on parameters are detected by searching up this heirarchy such
+ * annotations on parameters are detected by searching up this hierarchy such
* that these act as syntactic sugar, making:
*
*
- * This makes it clear that the user's filename should be included in the config file name,
+ * This makes it clear that the username 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. UserTest will attempt to run filenameIncludesUsername on every compatible data
* point 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.
+ * assumptions pass, but an assertion fails, the test fails. If no parameters can be found that satisfy all assumptions, the test fails.
*
* Defining general statements as theories allows data point reuse across a bunch of functionality tests and also
* allows automated tools to search for new, unexpected data points that expose bugs.
@@ -73,6 +73,11 @@ public Theories(Class> klass) throws InitializationError {
super(klass);
}
+ /** @since 4.13 */
+ protected Theories(TestClass testClass) throws InitializationError {
+ super(testClass);
+ }
+
@Override
protected void collectInitializationErrors(List errors) {
super.collectInitializationErrors(errors);
@@ -215,7 +220,7 @@ protected void runWithIncompleteAssignment(Assignments incomplete)
protected void runWithCompleteAssignment(final Assignments complete)
throws Throwable {
- new BlockJUnit4ClassRunner(getTestClass().getJavaClass()) {
+ new BlockJUnit4ClassRunner(getTestClass()) {
@Override
protected void collectInitializationErrors(
List errors) {
diff --git a/src/main/java/org/junit/experimental/theories/internal/Assignments.java b/src/main/java/org/junit/experimental/theories/internal/Assignments.java
index a94c8a5e00b1..6626797ef089 100644
--- a/src/main/java/org/junit/experimental/theories/internal/Assignments.java
+++ b/src/main/java/org/junit/experimental/theories/internal/Assignments.java
@@ -47,7 +47,7 @@ public static Assignments allUnassigned(Method testMethod,
}
public boolean isComplete() {
- return unassigned.size() == 0;
+ return unassigned.isEmpty();
}
public ParameterSignature nextUnassigned() {
@@ -55,11 +55,10 @@ public ParameterSignature nextUnassigned() {
}
public Assignments assignNext(PotentialAssignment source) {
- List assigned = new ArrayList(
- this.assigned);
- assigned.add(source);
+ List potentialAssignments = new ArrayList(assigned);
+ potentialAssignments.add(source);
- return new Assignments(assigned, unassigned.subList(1,
+ return new Assignments(potentialAssignments, unassigned.subList(1,
unassigned.size()), clazz);
}
@@ -77,7 +76,7 @@ public List potentialsForNextUnassigned()
ParameterSignature unassigned = nextUnassigned();
List assignments = getSupplier(unassigned).getValueSources(unassigned);
- if (assignments.size() == 0) {
+ if (assignments.isEmpty()) {
assignments = generateAssignmentsFromTypeAlone(unassigned);
}
diff --git a/src/main/java/org/junit/function/ThrowingRunnable.java b/src/main/java/org/junit/function/ThrowingRunnable.java
new file mode 100644
index 000000000000..d0eb782ccd30
--- /dev/null
+++ b/src/main/java/org/junit/function/ThrowingRunnable.java
@@ -0,0 +1,14 @@
+package org.junit.function;
+
+/**
+ * This interface facilitates the use of
+ * {@link org.junit.Assert#assertThrows(Class, ThrowingRunnable)} from Java 8. It allows method
+ * references to void methods (that declare checked exceptions) to be passed directly into
+ * {@code assertThrows}
+ * without wrapping. It is not meant to be implemented directly.
+ *
+ * @since 4.13
+ */
+public interface ThrowingRunnable {
+ void run() throws Throwable;
+}
diff --git a/src/main/java/org/junit/internal/ArrayComparisonFailure.java b/src/main/java/org/junit/internal/ArrayComparisonFailure.java
index 8627d6ec3308..d300e7e5866b 100644
--- a/src/main/java/org/junit/internal/ArrayComparisonFailure.java
+++ b/src/main/java/org/junit/internal/ArrayComparisonFailure.java
@@ -16,11 +16,12 @@ public class ArrayComparisonFailure extends AssertionError {
/*
* We have to use the f prefix until the next major release to ensure
- * serialization compatibility.
- * See https://github.com/junit-team/junit/issues/976
+ * serialization compatibility.
+ * See https://github.com/junit-team/junit4/issues/976
*/
private final List fIndices = new ArrayList();
private final String fMessage;
+ private final AssertionError fCause;
/**
* Construct a new ArrayComparisonFailure with an error text and the array's
@@ -32,7 +33,8 @@ public class ArrayComparisonFailure extends AssertionError {
*/
public ArrayComparisonFailure(String message, AssertionError cause, int index) {
this.fMessage = message;
- initCause(cause);
+ this.fCause = cause;
+ initCause(fCause);
addDimension(index);
}
@@ -40,6 +42,11 @@ public void addDimension(int index) {
fIndices.add(0, index);
}
+ @Override
+ public synchronized Throwable getCause() {
+ return super.getCause() == null ? fCause : super.getCause();
+ }
+
@Override
public String getMessage() {
StringBuilder sb = new StringBuilder();
diff --git a/src/main/java/org/junit/internal/AssumptionViolatedException.java b/src/main/java/org/junit/internal/AssumptionViolatedException.java
index 880d73f9af60..15c27af1b279 100644
--- a/src/main/java/org/junit/internal/AssumptionViolatedException.java
+++ b/src/main/java/org/junit/internal/AssumptionViolatedException.java
@@ -18,7 +18,7 @@ public class AssumptionViolatedException extends RuntimeException implements Sel
/*
* We have to use the f prefix until the next major release to ensure
* serialization compatibility.
- * See https://github.com/junit-team/junit/issues/976
+ * See https://github.com/junit-team/junit4/issues/976
*/
private final String fAssumption;
private final boolean fValueMatcher;
diff --git a/src/main/java/org/junit/internal/Checks.java b/src/main/java/org/junit/internal/Checks.java
new file mode 100644
index 000000000000..9724947f9977
--- /dev/null
+++ b/src/main/java/org/junit/internal/Checks.java
@@ -0,0 +1,37 @@
+package org.junit.internal;
+
+/** @since 4.13 */
+public final class Checks {
+
+ private Checks() {}
+
+ /**
+ * Checks that the given value is not {@code null}.
+ *
+ * @param value object reference to check
+ * @return the passed-in value, if not {@code null}
+ * @throws NullPointerException if {@code value} is {@code null}
+ */
+ public static T notNull(T value) {
+ if (value == null) {
+ throw new NullPointerException();
+ }
+ return value;
+ }
+
+ /**
+ * Checks that the given value is not {@code null}, using the given message
+ * as the exception message if an exception is thrown.
+ *
+ * @param value object reference to check
+ * @param message message to use if {@code value} is {@code null}
+ * @return the passed-in value, if not {@code null}
+ * @throws NullPointerException if {@code value} is {@code null}
+ */
+ public static T notNull(T value, String message) {
+ if (value == null) {
+ throw new NullPointerException(message);
+ }
+ return value;
+ }
+}
diff --git a/src/main/java/org/junit/internal/Classes.java b/src/main/java/org/junit/internal/Classes.java
index 154603d2e4f6..e8404f685504 100644
--- a/src/main/java/org/junit/internal/Classes.java
+++ b/src/main/java/org/junit/internal/Classes.java
@@ -6,13 +6,39 @@
* Miscellaneous functions dealing with classes.
*/
public class Classes {
+
+ /**
+ * Do not instantiate.
+ * @deprecated will be private soon.
+ */
+ @Deprecated
+ public Classes() {
+ }
+
/**
* Returns Class.forName for {@code className} using the current thread's class loader.
+ * If the current thread does not have a class loader, falls back to the class loader for
+ * {@link Classes}.
*
* @param className Name of the class.
* @throws ClassNotFoundException
*/
public static Class> getClass(String className) throws ClassNotFoundException {
- return Class.forName(className, true, currentThread().getContextClassLoader());
+ return getClass(className, Classes.class);
+ }
+
+ /**
+ * Returns Class.forName for {@code className} using the current thread's class loader.
+ * If the current thread does not have a class loader, falls back to the class loader for the
+ * passed-in class.
+ *
+ * @param className Name of the class.
+ * @param callingClass Class that is requesting a the class
+ * @throws ClassNotFoundException
+ * @since 4.13
+ */
+ public static Class> getClass(String className, Class> callingClass) throws ClassNotFoundException {
+ ClassLoader classLoader = currentThread().getContextClassLoader();
+ return Class.forName(className, true, classLoader == null ? callingClass.getClassLoader() : classLoader);
}
}
diff --git a/src/main/java/org/junit/internal/ComparisonCriteria.java b/src/main/java/org/junit/internal/ComparisonCriteria.java
index e6d49a4dc230..ed1c674907cb 100644
--- a/src/main/java/org/junit/internal/ComparisonCriteria.java
+++ b/src/main/java/org/junit/internal/ComparisonCriteria.java
@@ -25,6 +25,11 @@ public abstract class ComparisonCriteria {
*/
public void arrayEquals(String message, Object expecteds, Object actuals)
throws ArrayComparisonFailure {
+ arrayEquals(message, expecteds, actuals, true);
+ }
+
+ private void arrayEquals(String message, Object expecteds, Object actuals, boolean outer)
+ throws ArrayComparisonFailure {
if (expecteds == actuals
|| Arrays.deepEquals(new Object[] {expecteds}, new Object[] {actuals})) {
// The reflection-based loop below is potentially very slow, especially for primitive
@@ -34,19 +39,37 @@ public void arrayEquals(String message, Object expecteds, Object actuals)
}
String header = message == null ? "" : message + ": ";
- int expectedsLength = assertArraysAreSameLength(expecteds,
- actuals, header);
+ // Only include the user-provided message in the outer exception.
+ String exceptionMessage = outer ? header : "";
+
+ if (expecteds == null) {
+ Assert.fail(exceptionMessage + "expected array was null");
+ }
+ if (actuals == null) {
+ Assert.fail(exceptionMessage + "actual array was null");
+ }
+
+ int actualsLength = Array.getLength(actuals);
+ int expectedsLength = Array.getLength(expecteds);
+ if (actualsLength != expectedsLength) {
+ header += "array lengths differed, expected.length="
+ + expectedsLength + " actual.length=" + actualsLength + "; ";
+ }
+ int prefixLength = Math.min(actualsLength, expectedsLength);
- for (int i = 0; i < expectedsLength; i++) {
+ for (int i = 0; i < prefixLength; i++) {
Object expected = Array.get(expecteds, i);
Object actual = Array.get(actuals, i);
if (isArray(expected) && isArray(actual)) {
try {
- arrayEquals(message, expected, actual);
+ arrayEquals(message, expected, actual, false);
} catch (ArrayComparisonFailure e) {
e.addDimension(i);
throw e;
+ } catch (AssertionError e) {
+ // Array lengths differed.
+ throw new ArrayComparisonFailure(header, e, i);
}
} else {
try {
@@ -56,27 +79,53 @@ public void arrayEquals(String message, Object expecteds, Object actuals)
}
}
}
- }
- private boolean isArray(Object expected) {
- return expected != null && expected.getClass().isArray();
+ if (actualsLength != expectedsLength) {
+ Object expected = getToStringableArrayElement(expecteds, expectedsLength, prefixLength);
+ Object actual = getToStringableArrayElement(actuals, actualsLength, prefixLength);
+ try {
+ Assert.assertEquals(expected, actual);
+ } catch (AssertionError e) {
+ throw new ArrayComparisonFailure(header, e, prefixLength);
+ }
+ }
}
- 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");
+ private static final Object END_OF_ARRAY_SENTINEL = objectWithToString("end of array");
+
+ private Object getToStringableArrayElement(Object array, int length, int index) {
+ if (index < length) {
+ Object element = Array.get(array, index);
+ if (isArray(element)) {
+ return objectWithToString(componentTypeName(element.getClass()) + "[" + Array.getLength(element) + "]");
+ } else {
+ return element;
+ }
+ } else {
+ return END_OF_ARRAY_SENTINEL;
}
- 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);
+ }
+
+ private static Object objectWithToString(final String string) {
+ return new Object() {
+ @Override
+ public String toString() {
+ return string;
+ }
+ };
+ }
+
+ private String componentTypeName(Class> arrayClass) {
+ Class> componentType = arrayClass.getComponentType();
+ if (componentType.isArray()) {
+ return componentTypeName(componentType) + "[]";
+ } else {
+ return componentType.getName();
}
- return expectedsLength;
+ }
+
+ private boolean isArray(Object expected) {
+ return expected != null && expected.getClass().isArray();
}
protected abstract void assertElementsEqual(Object expected, Object actual);
diff --git a/src/main/java/org/junit/internal/TextListener.java b/src/main/java/org/junit/internal/TextListener.java
index 9aa56c75f5ec..d548aeb26e99 100644
--- a/src/main/java/org/junit/internal/TextListener.java
+++ b/src/main/java/org/junit/internal/TextListener.java
@@ -58,7 +58,7 @@ protected void printHeader(long runTime) {
protected void printFailures(Result result) {
List failures = result.getFailures();
- if (failures.size() == 0) {
+ if (failures.isEmpty()) {
return;
}
if (failures.size() == 1) {
@@ -74,7 +74,7 @@ protected void printFailures(Result result) {
protected void printFailure(Failure each, String prefix) {
getWriter().println(prefix + ") " + each.getTestHeader());
- getWriter().print(each.getTrace());
+ getWriter().print(each.getTrimmedTrace());
}
protected void printFooter(Result result) {
diff --git a/src/main/java/org/junit/internal/Throwables.java b/src/main/java/org/junit/internal/Throwables.java
index 86dceeff2559..67d6b2bc06ec 100644
--- a/src/main/java/org/junit/internal/Throwables.java
+++ b/src/main/java/org/junit/internal/Throwables.java
@@ -1,5 +1,17 @@
package org.junit.internal;
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.lang.reflect.Method;
+import java.util.AbstractList;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
/**
* Miscellaneous functions dealing with {@code Throwable}.
*
@@ -39,4 +51,220 @@ public static Exception rethrowAsException(Throwable e) throws Exception {
private static void rethrow(Throwable e) throws T {
throw (T) e;
}
+
+ /**
+ * Returns the stacktrace of the given Throwable as a String.
+ *
+ * @since 4.13
+ */
+ public static String getStacktrace(Throwable exception) {
+ StringWriter stringWriter = new StringWriter();
+ PrintWriter writer = new PrintWriter(stringWriter);
+ exception.printStackTrace(writer);
+ return stringWriter.toString();
+ }
+
+ /**
+ * Gets a trimmed version of the stack trace of the given exception. Stack trace
+ * elements that are below the test method are filtered out.
+ *
+ * @return a trimmed stack trace, or the original trace if trimming wasn't possible
+ */
+ public static String getTrimmedStackTrace(Throwable exception) {
+ List trimmedStackTraceLines = getTrimmedStackTraceLines(exception);
+ if (trimmedStackTraceLines.isEmpty()) {
+ return getFullStackTrace(exception);
+ }
+
+ StringBuilder result = new StringBuilder(exception.toString());
+ appendStackTraceLines(trimmedStackTraceLines, result);
+ appendStackTraceLines(getCauseStackTraceLines(exception), result);
+ return result.toString();
+ }
+
+ private static List getTrimmedStackTraceLines(Throwable exception) {
+ List stackTraceElements = Arrays.asList(exception.getStackTrace());
+ int linesToInclude = stackTraceElements.size();
+
+ State state = State.PROCESSING_OTHER_CODE;
+ for (StackTraceElement stackTraceElement : asReversedList(stackTraceElements)) {
+ state = state.processStackTraceElement(stackTraceElement);
+ if (state == State.DONE) {
+ List trimmedLines = new ArrayList(linesToInclude + 2);
+ trimmedLines.add("");
+ for (StackTraceElement each : stackTraceElements.subList(0, linesToInclude)) {
+ trimmedLines.add("\tat " + each);
+ }
+ if (exception.getCause() != null) {
+ trimmedLines.add("\t... " + (stackTraceElements.size() - trimmedLines.size()) + " trimmed");
+ }
+ return trimmedLines;
+ }
+ linesToInclude--;
+ }
+ return Collections.emptyList();
+ }
+
+ private static final Method getSuppressed = initGetSuppressed();
+
+ private static Method initGetSuppressed() {
+ try {
+ return Throwable.class.getMethod("getSuppressed");
+ } catch (Throwable e) {
+ return null;
+ }
+ }
+
+ private static boolean hasSuppressed(Throwable exception) {
+ if (getSuppressed == null) {
+ return false;
+ }
+ try {
+ Throwable[] suppressed = (Throwable[]) getSuppressed.invoke(exception);
+ return suppressed.length != 0;
+ } catch (Throwable e) {
+ return false;
+ }
+ }
+
+ private static List getCauseStackTraceLines(Throwable exception) {
+ if (exception.getCause() != null || hasSuppressed(exception)) {
+ String fullTrace = getFullStackTrace(exception);
+ BufferedReader reader = new BufferedReader(
+ new StringReader(fullTrace.substring(exception.toString().length())));
+ List causedByLines = new ArrayList();
+
+ try {
+ String line;
+ while ((line = reader.readLine()) != null) {
+ if (line.startsWith("Caused by: ") || line.trim().startsWith("Suppressed: ")) {
+ causedByLines.add(line);
+ while ((line = reader.readLine()) != null) {
+ causedByLines.add(line);
+ }
+ return causedByLines;
+ }
+ }
+ } catch (IOException e) {
+ // We should never get here, because we are reading from a StringReader
+ }
+ }
+
+ return Collections.emptyList();
+ }
+
+ private static String getFullStackTrace(Throwable exception) {
+ StringWriter stringWriter = new StringWriter();
+ PrintWriter writer = new PrintWriter(stringWriter);
+ exception.printStackTrace(writer);
+ return stringWriter.toString();
+ }
+
+ private static void appendStackTraceLines(
+ List stackTraceLines, StringBuilder destBuilder) {
+ for (String stackTraceLine : stackTraceLines) {
+ destBuilder.append(String.format("%s%n", stackTraceLine));
+ }
+ }
+
+ private static List asReversedList(final List list) {
+ return new AbstractList() {
+
+ @Override
+ public T get(int index) {
+ return list.get(list.size() - index - 1);
+ }
+
+ @Override
+ public int size() {
+ return list.size();
+ }
+ };
+ }
+
+ private enum State {
+ PROCESSING_OTHER_CODE {
+ @Override public State processLine(String methodName) {
+ if (isTestFrameworkMethod(methodName)) {
+ return PROCESSING_TEST_FRAMEWORK_CODE;
+ }
+ return this;
+ }
+ },
+ PROCESSING_TEST_FRAMEWORK_CODE {
+ @Override public State processLine(String methodName) {
+ if (isReflectionMethod(methodName)) {
+ return PROCESSING_REFLECTION_CODE;
+ } else if (isTestFrameworkMethod(methodName)) {
+ return this;
+ }
+ return PROCESSING_OTHER_CODE;
+ }
+ },
+ PROCESSING_REFLECTION_CODE {
+ @Override public State processLine(String methodName) {
+ if (isReflectionMethod(methodName)) {
+ return this;
+ } else if (isTestFrameworkMethod(methodName)) {
+ // This is here to handle TestCase.runBare() calling TestCase.runTest().
+ return PROCESSING_TEST_FRAMEWORK_CODE;
+ }
+ return DONE;
+ }
+ },
+ DONE {
+ @Override public State processLine(String methodName) {
+ return this;
+ }
+ };
+
+ /** Processes a stack trace element method name, possibly moving to a new state. */
+ protected abstract State processLine(String methodName);
+
+ /** Processes a stack trace element, possibly moving to a new state. */
+ public final State processStackTraceElement(StackTraceElement element) {
+ return processLine(element.getClassName() + "." + element.getMethodName() + "()");
+ }
+ }
+
+ private static final String[] TEST_FRAMEWORK_METHOD_NAME_PREFIXES = {
+ "org.junit.runner.",
+ "org.junit.runners.",
+ "org.junit.experimental.runners.",
+ "org.junit.internal.",
+ "junit.",
+ };
+
+ private static final String[] TEST_FRAMEWORK_TEST_METHOD_NAME_PREFIXES = {
+ "org.junit.internal.StackTracesTest",
+ };
+
+ private static boolean isTestFrameworkMethod(String methodName) {
+ return isMatchingMethod(methodName, TEST_FRAMEWORK_METHOD_NAME_PREFIXES) &&
+ !isMatchingMethod(methodName, TEST_FRAMEWORK_TEST_METHOD_NAME_PREFIXES);
+ }
+
+ private static final String[] REFLECTION_METHOD_NAME_PREFIXES = {
+ "sun.reflect.",
+ "java.lang.reflect.",
+ "jdk.internal.reflect.",
+ "org.junit.rules.RunRules.(",
+ "org.junit.rules.RunRules.applyAll(", // calls TestRules
+ "org.junit.runners.RuleContainer.apply(", // calls MethodRules & TestRules
+ "junit.framework.TestCase.runBare(", // runBare() directly calls setUp() and tearDown()
+ };
+
+ private static boolean isReflectionMethod(String methodName) {
+ return isMatchingMethod(methodName, REFLECTION_METHOD_NAME_PREFIXES);
+ }
+
+ private static boolean isMatchingMethod(String methodName, String[] methodNamePrefixes) {
+ for (String methodNamePrefix : methodNamePrefixes) {
+ if (methodName.startsWith(methodNamePrefix)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
}
diff --git a/src/main/java/org/junit/internal/builders/AllDefaultPossibilitiesBuilder.java b/src/main/java/org/junit/internal/builders/AllDefaultPossibilitiesBuilder.java
index d86ec950f655..8704a546b5f2 100644
--- a/src/main/java/org/junit/internal/builders/AllDefaultPossibilitiesBuilder.java
+++ b/src/main/java/org/junit/internal/builders/AllDefaultPossibilitiesBuilder.java
@@ -9,6 +9,17 @@
public class AllDefaultPossibilitiesBuilder extends RunnerBuilder {
private final boolean canUseSuiteMethod;
+ /**
+ * @since 4.13
+ */
+ public AllDefaultPossibilitiesBuilder() {
+ canUseSuiteMethod = true;
+ }
+
+ /**
+ * @deprecated used {@link #AllDefaultPossibilitiesBuilder()}.
+ */
+ @Deprecated
public AllDefaultPossibilitiesBuilder(boolean canUseSuiteMethod) {
this.canUseSuiteMethod = canUseSuiteMethod;
}
diff --git a/src/main/java/org/junit/internal/builders/JUnit4Builder.java b/src/main/java/org/junit/internal/builders/JUnit4Builder.java
index 6a0067852cc6..7959e75239db 100644
--- a/src/main/java/org/junit/internal/builders/JUnit4Builder.java
+++ b/src/main/java/org/junit/internal/builders/JUnit4Builder.java
@@ -1,12 +1,12 @@
package org.junit.internal.builders;
import org.junit.runner.Runner;
-import org.junit.runners.BlockJUnit4ClassRunner;
+import org.junit.runners.JUnit4;
import org.junit.runners.model.RunnerBuilder;
public class JUnit4Builder extends RunnerBuilder {
@Override
public Runner runnerForClass(Class> testClass) throws Throwable {
- return new BlockJUnit4ClassRunner(testClass);
+ return new JUnit4(testClass);
}
-}
\ No newline at end of file
+}
diff --git a/src/main/java/org/junit/internal/management/FakeRuntimeMXBean.java b/src/main/java/org/junit/internal/management/FakeRuntimeMXBean.java
new file mode 100644
index 000000000000..477b1509ce45
--- /dev/null
+++ b/src/main/java/org/junit/internal/management/FakeRuntimeMXBean.java
@@ -0,0 +1,21 @@
+package org.junit.internal.management;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * No-op implementation of RuntimeMXBean when the platform doesn't provide it.
+ */
+class FakeRuntimeMXBean implements RuntimeMXBean {
+
+ /**
+ * {@inheritDoc}
+ *
+ *
Always returns an empty list.
+ */
+ public List getInputArguments() {
+ return Collections.emptyList();
+ }
+
+}
+
diff --git a/src/main/java/org/junit/internal/management/FakeThreadMXBean.java b/src/main/java/org/junit/internal/management/FakeThreadMXBean.java
new file mode 100644
index 000000000000..893f2e3ea583
--- /dev/null
+++ b/src/main/java/org/junit/internal/management/FakeThreadMXBean.java
@@ -0,0 +1,27 @@
+package org.junit.internal.management;
+
+/**
+ * No-op implementation of ThreadMXBean when the platform doesn't provide it.
+ */
+final class FakeThreadMXBean implements ThreadMXBean {
+
+ /**
+ * {@inheritDoc}
+ *
+ *
Always throws an {@link UnsupportedOperationException}
+ */
+ public long getThreadCpuTime(long id) {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ *
Always returns false.
+ */
+ public boolean isThreadCpuTimeSupported() {
+ return false;
+ }
+
+}
+
diff --git a/src/main/java/org/junit/internal/management/ManagementFactory.java b/src/main/java/org/junit/internal/management/ManagementFactory.java
new file mode 100644
index 000000000000..5be1447532e9
--- /dev/null
+++ b/src/main/java/org/junit/internal/management/ManagementFactory.java
@@ -0,0 +1,77 @@
+package org.junit.internal.management;
+
+import org.junit.internal.Classes;
+
+import java.lang.reflect.InvocationTargetException;
+
+/**
+ * Reflective wrapper around {@link java.lang.management.ManagementFactory}
+ */
+public class ManagementFactory {
+ private static final class FactoryHolder {
+ private static final Class> MANAGEMENT_FACTORY_CLASS;
+
+ static {
+ Class> managementFactoryClass = null;
+ try {
+ managementFactoryClass = Classes.getClass("java.lang.management.ManagementFactory");
+ } catch (ClassNotFoundException e) {
+ // do nothing, managementFactoryClass will be none on failure
+ }
+ MANAGEMENT_FACTORY_CLASS = managementFactoryClass;
+ }
+
+ static Object getBeanObject(String methodName) {
+ if (MANAGEMENT_FACTORY_CLASS != null) {
+ try {
+ return MANAGEMENT_FACTORY_CLASS.getMethod(methodName).invoke(null);
+ } catch (IllegalAccessException e) {
+ // fallthrough
+ } catch (IllegalArgumentException e) {
+ // fallthrough
+ } catch (InvocationTargetException e) {
+ // fallthrough
+ } catch (NoSuchMethodException e) {
+ // fallthrough
+ } catch (SecurityException e) {
+ // fallthrough
+ }
+ }
+ return null;
+ }
+ }
+
+ private static final class RuntimeHolder {
+ private static final RuntimeMXBean RUNTIME_MX_BEAN =
+ getBean(FactoryHolder.getBeanObject("getRuntimeMXBean"));
+
+ private static final RuntimeMXBean getBean(Object runtimeMxBean) {
+ return runtimeMxBean != null
+ ? new ReflectiveRuntimeMXBean(runtimeMxBean) : new FakeRuntimeMXBean();
+ }
+ }
+
+ private static final class ThreadHolder {
+ private static final ThreadMXBean THREAD_MX_BEAN =
+ getBean(FactoryHolder.getBeanObject("getThreadMXBean"));
+
+ private static final ThreadMXBean getBean(Object threadMxBean) {
+ return threadMxBean != null
+ ? new ReflectiveThreadMXBean(threadMxBean) : new FakeThreadMXBean();
+ }
+ }
+
+ /**
+ * @see java.lang.management.ManagementFactory#getRuntimeMXBean()
+ */
+ public static RuntimeMXBean getRuntimeMXBean() {
+ return RuntimeHolder.RUNTIME_MX_BEAN;
+ }
+
+ /**
+ * @see java.lang.management.ManagementFactory#getThreadMXBean()
+ */
+ public static ThreadMXBean getThreadMXBean() {
+ return ThreadHolder.THREAD_MX_BEAN;
+ }
+}
diff --git a/src/main/java/org/junit/internal/management/ReflectiveRuntimeMXBean.java b/src/main/java/org/junit/internal/management/ReflectiveRuntimeMXBean.java
new file mode 100644
index 000000000000..289587a44776
--- /dev/null
+++ b/src/main/java/org/junit/internal/management/ReflectiveRuntimeMXBean.java
@@ -0,0 +1,61 @@
+package org.junit.internal.management;
+
+import org.junit.internal.Classes;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Implementation of {@link RuntimeMXBean} using the JVM reflectively.
+ */
+final class ReflectiveRuntimeMXBean implements RuntimeMXBean {
+ private final Object runtimeMxBean;
+
+ private static final class Holder {
+ private static final Method getInputArgumentsMethod;
+ static {
+ Method inputArguments = null;
+ try {
+ Class> threadMXBeanClass = Classes.getClass("java.lang.management.RuntimeMXBean");
+ inputArguments = threadMXBeanClass.getMethod("getInputArguments");
+ } catch (ClassNotFoundException e) {
+ // do nothing, input arguments will be null on failure
+ } catch (NoSuchMethodException e) {
+ // do nothing, input arguments will be null on failure
+ } catch (SecurityException e) {
+ // do nothing, input arguments will be null on failure
+ }
+ getInputArgumentsMethod = inputArguments;
+ }
+ }
+
+ ReflectiveRuntimeMXBean(Object runtimeMxBean) {
+ super();
+ this.runtimeMxBean = runtimeMxBean;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @SuppressWarnings("unchecked")
+ public List getInputArguments() {
+ if (Holder.getInputArgumentsMethod != null) {
+ try {
+ return (List) Holder.getInputArgumentsMethod.invoke(runtimeMxBean);
+ } catch (ClassCastException e) { // no multi-catch with source level 6
+ // fallthrough
+ } catch (IllegalAccessException e) {
+ // fallthrough
+ } catch (IllegalArgumentException e) {
+ // fallthrough
+ } catch (InvocationTargetException e) {
+ // fallthrough
+ }
+ }
+ return Collections.emptyList();
+ }
+
+}
+
diff --git a/src/main/java/org/junit/internal/management/ReflectiveThreadMXBean.java b/src/main/java/org/junit/internal/management/ReflectiveThreadMXBean.java
new file mode 100644
index 000000000000..bc741bec8e61
--- /dev/null
+++ b/src/main/java/org/junit/internal/management/ReflectiveThreadMXBean.java
@@ -0,0 +1,92 @@
+package org.junit.internal.management;
+
+import org.junit.internal.Classes;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+/**
+ * Implementation of {@link ThreadMXBean} using the JVM reflectively.
+ */
+final class ReflectiveThreadMXBean implements ThreadMXBean {
+ private final Object threadMxBean;
+
+
+ private static final class Holder {
+ static final Method getThreadCpuTimeMethod;
+ static final Method isThreadCpuTimeSupportedMethod;
+
+ private static final String FAILURE_MESSAGE = "Unable to access ThreadMXBean";
+
+ static {
+ Method threadCpuTime = null;
+ Method threadCpuTimeSupported = null;
+ try {
+ Class> threadMXBeanClass = Classes.getClass("java.lang.management.ThreadMXBean");
+ threadCpuTime = threadMXBeanClass.getMethod("getThreadCpuTime", long.class);
+ threadCpuTimeSupported = threadMXBeanClass.getMethod("isThreadCpuTimeSupported");
+ } catch (ClassNotFoundException e) {
+ // do nothing, the methods will be null on failure
+ } catch (NoSuchMethodException e) {
+ // do nothing, the methods will be null on failure
+ } catch (SecurityException e) {
+ // do nothing, the methods will be null on failure
+ }
+ getThreadCpuTimeMethod = threadCpuTime;
+ isThreadCpuTimeSupportedMethod = threadCpuTimeSupported;
+ }
+ }
+
+ ReflectiveThreadMXBean(Object threadMxBean) {
+ super();
+ this.threadMxBean = threadMxBean;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public long getThreadCpuTime(long id) {
+ if (Holder.getThreadCpuTimeMethod != null) {
+ Exception error = null;
+ try {
+ return (Long) Holder.getThreadCpuTimeMethod.invoke(threadMxBean, id);
+ } catch (ClassCastException e) {
+ error = e;
+ // fallthrough
+ } catch (IllegalAccessException e) {
+ error = e;
+ // fallthrough
+ } catch (IllegalArgumentException e) {
+ error = e;
+ // fallthrough
+ } catch (InvocationTargetException e) {
+ error = e;
+ // fallthrough
+ }
+ throw new UnsupportedOperationException(Holder.FAILURE_MESSAGE, error);
+ }
+ throw new UnsupportedOperationException(Holder.FAILURE_MESSAGE);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean isThreadCpuTimeSupported() {
+ if (Holder.isThreadCpuTimeSupportedMethod != null) {
+ try {
+ return (Boolean) Holder.isThreadCpuTimeSupportedMethod.invoke(threadMxBean);
+ } catch (ClassCastException e) {
+ // fallthrough
+ } catch (IllegalAccessException e) {
+ // fallthrough
+ } catch (IllegalArgumentException e) {
+ // fallthrough
+ } catch (InvocationTargetException e) {
+ // fallthrough
+ }
+ }
+ return false;
+ }
+
+}
+
diff --git a/src/main/java/org/junit/internal/management/RuntimeMXBean.java b/src/main/java/org/junit/internal/management/RuntimeMXBean.java
new file mode 100644
index 000000000000..84f8861f2fed
--- /dev/null
+++ b/src/main/java/org/junit/internal/management/RuntimeMXBean.java
@@ -0,0 +1,14 @@
+package org.junit.internal.management;
+
+import java.util.List;
+
+/**
+ * Wrapper for {@link java.lang.management.RuntimeMXBean}.
+ */
+public interface RuntimeMXBean {
+
+ /**
+ * @see java.lang.management.RuntimeMXBean#getInputArguments()
+ */
+ List getInputArguments();
+}
diff --git a/src/main/java/org/junit/internal/management/ThreadMXBean.java b/src/main/java/org/junit/internal/management/ThreadMXBean.java
new file mode 100644
index 000000000000..f9225c9fb888
--- /dev/null
+++ b/src/main/java/org/junit/internal/management/ThreadMXBean.java
@@ -0,0 +1,17 @@
+package org.junit.internal.management;
+
+/**
+ * Wrapper for {@link java.lang.management.ThreadMXBean}.
+ */
+public interface ThreadMXBean {
+ /**
+ * @see java.lang.management.ThreadMXBean#getThreadCpuTime(long)
+ */
+ long getThreadCpuTime(long id);
+
+ /**
+ * @see java.lang.management.ThreadMXBean#isThreadCpuTimeSupported()
+ */
+ boolean isThreadCpuTimeSupported();
+}
+
diff --git a/src/main/java/org/junit/internal/matchers/StacktracePrintingMatcher.java b/src/main/java/org/junit/internal/matchers/StacktracePrintingMatcher.java
index 5d45ba3d03af..93a6827678d0 100644
--- a/src/main/java/org/junit/internal/matchers/StacktracePrintingMatcher.java
+++ b/src/main/java/org/junit/internal/matchers/StacktracePrintingMatcher.java
@@ -1,12 +1,11 @@
package org.junit.internal.matchers;
-import java.io.PrintWriter;
-import java.io.StringWriter;
-
import org.hamcrest.Description;
import org.hamcrest.Factory;
import org.hamcrest.Matcher;
+import org.junit.internal.Throwables;
+
/**
* A matcher that delegates to throwableMatcher and in addition appends the
* stacktrace of the actual Throwable in case of a mismatch.
@@ -37,9 +36,7 @@ protected void describeMismatchSafely(T item, Description description) {
}
private String readStacktrace(Throwable throwable) {
- StringWriter stringWriter = new StringWriter();
- throwable.printStackTrace(new PrintWriter(stringWriter));
- return stringWriter.toString();
+ return Throwables.getStacktrace(throwable);
}
@Factory
diff --git a/src/main/java/org/junit/internal/matchers/ThrowableCauseMatcher.java b/src/main/java/org/junit/internal/matchers/ThrowableCauseMatcher.java
index 22ce8bd59341..6e2ff5e3d565 100644
--- a/src/main/java/org/junit/internal/matchers/ThrowableCauseMatcher.java
+++ b/src/main/java/org/junit/internal/matchers/ThrowableCauseMatcher.java
@@ -14,9 +14,9 @@
public class ThrowableCauseMatcher extends
TypeSafeMatcher {
- private final Matcher extends Throwable> causeMatcher;
+ private final Matcher> causeMatcher;
- public ThrowableCauseMatcher(Matcher extends Throwable> causeMatcher) {
+ public ThrowableCauseMatcher(Matcher> causeMatcher) {
this.causeMatcher = causeMatcher;
}
@@ -44,7 +44,7 @@ protected void describeMismatchSafely(T item, Description description) {
* @param type of the outer exception
*/
@Factory
- public static Matcher hasCause(final Matcher extends Throwable> matcher) {
+ public static Matcher hasCause(final Matcher> matcher) {
return new ThrowableCauseMatcher(matcher);
}
}
\ No newline at end of file
diff --git a/src/main/java/org/junit/internal/matchers/TypeSafeMatcher.java b/src/main/java/org/junit/internal/matchers/TypeSafeMatcher.java
index 4e2cc1264d09..fb25982bbd4d 100644
--- a/src/main/java/org/junit/internal/matchers/TypeSafeMatcher.java
+++ b/src/main/java/org/junit/internal/matchers/TypeSafeMatcher.java
@@ -40,7 +40,7 @@ private static Class> findExpectedType(Class> fromClass) {
}
private static boolean isMatchesSafelyMethod(Method method) {
- return method.getName().equals("matchesSafely")
+ return "matchesSafely".equals(method.getName())
&& method.getParameterTypes().length == 1
&& !method.isSynthetic();
}
diff --git a/src/main/java/org/junit/internal/requests/ClassRequest.java b/src/main/java/org/junit/internal/requests/ClassRequest.java
index 3d6b10066099..d60e36062d81 100644
--- a/src/main/java/org/junit/internal/requests/ClassRequest.java
+++ b/src/main/java/org/junit/internal/requests/ClassRequest.java
@@ -1,20 +1,18 @@
package org.junit.internal.requests;
import org.junit.internal.builders.AllDefaultPossibilitiesBuilder;
-import org.junit.runner.Request;
+import org.junit.internal.builders.SuiteMethodBuilder;
import org.junit.runner.Runner;
+import org.junit.runners.model.RunnerBuilder;
-public class ClassRequest extends Request {
- private final Object runnerLock = new Object();
-
+public class ClassRequest extends MemoizingRequest {
/*
* We have to use the f prefix, because IntelliJ's JUnit4IdeaTestRunner uses
* reflection to access this field. See
- * https://github.com/junit-team/junit/issues/960
+ * https://github.com/junit-team/junit4/issues/960
*/
private final Class> fTestClass;
private final boolean canUseSuiteMethod;
- private volatile Runner runner;
public ClassRequest(Class> testClass, boolean canUseSuiteMethod) {
this.fTestClass = testClass;
@@ -26,14 +24,31 @@ public ClassRequest(Class> testClass) {
}
@Override
- public Runner getRunner() {
- if (runner == null) {
- synchronized (runnerLock) {
- if (runner == null) {
- runner = new AllDefaultPossibilitiesBuilder(canUseSuiteMethod).safeRunnerForClass(fTestClass);
- }
+ protected Runner createRunner() {
+ return new CustomAllDefaultPossibilitiesBuilder().safeRunnerForClass(fTestClass);
+ }
+
+ private class CustomAllDefaultPossibilitiesBuilder extends AllDefaultPossibilitiesBuilder {
+
+ @Override
+ protected RunnerBuilder suiteMethodBuilder() {
+ return new CustomSuiteMethodBuilder();
+ }
+ }
+
+ /*
+ * Customization of {@link SuiteMethodBuilder} that prevents use of the
+ * suite method when creating a runner for fTestClass when canUseSuiteMethod
+ * is false.
+ */
+ private class CustomSuiteMethodBuilder extends SuiteMethodBuilder {
+
+ @Override
+ public Runner runnerForClass(Class> testClass) throws Throwable {
+ if (testClass == fTestClass && !canUseSuiteMethod) {
+ return null;
}
+ return super.runnerForClass(testClass);
}
- return runner;
}
}
\ No newline at end of file
diff --git a/src/main/java/org/junit/internal/requests/FilterRequest.java b/src/main/java/org/junit/internal/requests/FilterRequest.java
index 066cba322205..5f00399557c2 100644
--- a/src/main/java/org/junit/internal/requests/FilterRequest.java
+++ b/src/main/java/org/junit/internal/requests/FilterRequest.java
@@ -14,7 +14,7 @@ public final class FilterRequest extends Request {
/*
* We have to use the f prefix, because IntelliJ's JUnit4IdeaTestRunner uses
* reflection to access this field. See
- * https://github.com/junit-team/junit/issues/960
+ * https://github.com/junit-team/junit4/issues/960
*/
private final Filter fFilter;
diff --git a/src/main/java/org/junit/internal/requests/MemoizingRequest.java b/src/main/java/org/junit/internal/requests/MemoizingRequest.java
new file mode 100644
index 000000000000..191c23022536
--- /dev/null
+++ b/src/main/java/org/junit/internal/requests/MemoizingRequest.java
@@ -0,0 +1,30 @@
+package org.junit.internal.requests;
+
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+import org.junit.runner.Request;
+import org.junit.runner.Runner;
+
+abstract class MemoizingRequest extends Request {
+ private final Lock runnerLock = new ReentrantLock();
+ private volatile Runner runner;
+
+ @Override
+ public final Runner getRunner() {
+ if (runner == null) {
+ runnerLock.lock();
+ try {
+ if (runner == null) {
+ runner = createRunner();
+ }
+ } finally {
+ runnerLock.unlock();
+ }
+ }
+ return runner;
+ }
+
+ /** Creates the {@link Runner} to return from {@link #getRunner()}. Called at most once. */
+ protected abstract Runner createRunner();
+}
diff --git a/src/main/java/org/junit/internal/requests/OrderingRequest.java b/src/main/java/org/junit/internal/requests/OrderingRequest.java
new file mode 100644
index 000000000000..441e595a3680
--- /dev/null
+++ b/src/main/java/org/junit/internal/requests/OrderingRequest.java
@@ -0,0 +1,29 @@
+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.InvalidOrderingException;
+import org.junit.runner.manipulation.Ordering;
+
+/** @since 4.13 */
+public class OrderingRequest extends MemoizingRequest {
+ private final Request request;
+ private final Ordering ordering;
+
+ public OrderingRequest(Request request, Ordering ordering) {
+ this.request = request;
+ this.ordering = ordering;
+ }
+
+ @Override
+ protected Runner createRunner() {
+ Runner runner = request.getRunner();
+ try {
+ ordering.apply(runner);
+ } catch (InvalidOrderingException e) {
+ return new ErrorReportingRunner(ordering.getClass(), e);
+ }
+ return runner;
+ }
+}
diff --git a/src/main/java/org/junit/internal/runners/ErrorReportingRunner.java b/src/main/java/org/junit/internal/runners/ErrorReportingRunner.java
index 1d32bebe1f09..f52abab69439 100644
--- a/src/main/java/org/junit/internal/runners/ErrorReportingRunner.java
+++ b/src/main/java/org/junit/internal/runners/ErrorReportingRunner.java
@@ -1,33 +1,44 @@
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.InvalidTestClassError;
import org.junit.runners.model.InitializationError;
+import static java.util.Collections.singletonList;
+
public class ErrorReportingRunner extends Runner {
private final List causes;
- private final Class> testClass;
+ private final String classNames;
public ErrorReportingRunner(Class> testClass, Throwable cause) {
- if (testClass == null) {
- throw new NullPointerException("Test class cannot be null");
+ this(cause, testClass);
+ }
+
+ public ErrorReportingRunner(Throwable cause, Class>... testClasses) {
+ if (testClasses == null || testClasses.length == 0) {
+ throw new NullPointerException("Test classes cannot be null or empty");
}
- this.testClass = testClass;
+ for (Class> testClass : testClasses) {
+ if (testClass == null) {
+ throw new NullPointerException("Test class cannot be null");
+ }
+ }
+ classNames = getClassNames(testClasses);
causes = getCauses(cause);
}
-
+
@Override
public Description getDescription() {
- Description description = Description.createSuiteDescription(testClass);
+ Description description = Description.createSuiteDescription(classNames);
for (Throwable each : causes) {
- description.addChild(describeCause(each));
+ description.addChild(describeCause());
}
return description;
}
@@ -39,11 +50,25 @@ public void run(RunNotifier notifier) {
}
}
+ private String getClassNames(Class>... testClasses) {
+ final StringBuilder builder = new StringBuilder();
+ for (Class> testClass : testClasses) {
+ if (builder.length() != 0) {
+ builder.append(", ");
+ }
+ builder.append(testClass.getName());
+ }
+ return builder.toString();
+ }
+
@SuppressWarnings("deprecation")
private List getCauses(Throwable cause) {
if (cause instanceof InvocationTargetException) {
return getCauses(cause.getCause());
}
+ if (cause instanceof InvalidTestClassError) {
+ return singletonList(cause);
+ }
if (cause instanceof InitializationError) {
return ((InitializationError) cause).getCauses();
}
@@ -51,16 +76,15 @@ private List getCauses(Throwable cause) {
return ((org.junit.internal.runners.InitializationError) cause)
.getCauses();
}
- return Arrays.asList(cause);
+ return singletonList(cause);
}
- private Description describeCause(Throwable child) {
- return Description.createTestDescription(testClass,
- "initializationError");
+ private Description describeCause() {
+ return Description.createTestDescription(classNames, "initializationError");
}
private void runCause(Throwable child, RunNotifier notifier) {
- Description description = describeCause(child);
+ Description description = describeCause();
notifier.fireTestStarted(description);
notifier.fireTestFailure(new Failure(description, child));
notifier.fireTestFinished(description);
diff --git a/src/main/java/org/junit/internal/runners/InitializationError.java b/src/main/java/org/junit/internal/runners/InitializationError.java
index 52065ecc6068..484f58d26d69 100644
--- a/src/main/java/org/junit/internal/runners/InitializationError.java
+++ b/src/main/java/org/junit/internal/runners/InitializationError.java
@@ -15,7 +15,7 @@ public class InitializationError extends Exception {
/*
* We have to use the f prefix until the next major release to ensure
* serialization compatibility.
- * See https://github.com/junit-team/junit/issues/976
+ * See https://github.com/junit-team/junit4/issues/976
*/
private final List fErrors;
diff --git a/src/main/java/org/junit/internal/runners/JUnit38ClassRunner.java b/src/main/java/org/junit/internal/runners/JUnit38ClassRunner.java
index 631fcf2e5904..0d51541adcf8 100644
--- a/src/main/java/org/junit/internal/runners/JUnit38ClassRunner.java
+++ b/src/main/java/org/junit/internal/runners/JUnit38ClassRunner.java
@@ -1,5 +1,8 @@
package org.junit.internal.runners;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Method;
+
import junit.extensions.TestDecorator;
import junit.framework.AssertionFailedError;
import junit.framework.Test;
@@ -12,15 +15,16 @@
import org.junit.runner.Runner;
import org.junit.runner.manipulation.Filter;
import org.junit.runner.manipulation.Filterable;
+import org.junit.runner.manipulation.Orderer;
+import org.junit.runner.manipulation.InvalidOrderingException;
import org.junit.runner.manipulation.NoTestsRemainException;
+import org.junit.runner.manipulation.Orderable;
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 java.lang.annotation.Annotation;
-import java.lang.reflect.Method;
-public class JUnit38ClassRunner extends Runner implements Filterable, Sortable {
+public class JUnit38ClassRunner extends Runner implements Filterable, Orderable {
private static final class OldTestClassAdaptingListener implements
TestListener {
private final RunNotifier notifier;
@@ -170,6 +174,18 @@ public void sort(Sorter sorter) {
}
}
+ /**
+ * {@inheritDoc}
+ *
+ * @since 4.13
+ */
+ public void order(Orderer orderer) throws InvalidOrderingException {
+ if (getTest() instanceof Orderable) {
+ Orderable adapter = (Orderable) getTest();
+ adapter.order(orderer);
+ }
+ }
+
private void setTest(Test test) {
this.test = test;
}
diff --git a/src/main/java/org/junit/internal/runners/MethodValidator.java b/src/main/java/org/junit/internal/runners/MethodValidator.java
index ba9c9d1546ba..e656ee50efea 100644
--- a/src/main/java/org/junit/internal/runners/MethodValidator.java
+++ b/src/main/java/org/junit/internal/runners/MethodValidator.java
@@ -86,7 +86,7 @@ private void validateTestMethods(Class extends Annotation> annotation,
}
if (each.getReturnType() != Void.TYPE) {
errors.add(new Exception("Method " + each.getName()
- + " should be void"));
+ + "should have a return type of void"));
}
if (each.getParameterTypes().length != 0) {
errors.add(new Exception("Method " + each.getName()
diff --git a/src/main/java/org/junit/internal/runners/TestClass.java b/src/main/java/org/junit/internal/runners/TestClass.java
index 1abaeeab688a..6d24f4fb05af 100644
--- a/src/main/java/org/junit/internal/runners/TestClass.java
+++ b/src/main/java/org/junit/internal/runners/TestClass.java
@@ -85,7 +85,7 @@ private boolean isShadowed(Method current, Method previous) {
}
private List> getSuperClasses(Class> testClass) {
- ArrayList> results = new ArrayList>();
+ List> results = new ArrayList>();
Class> current = testClass;
while (current != null) {
results.add(current);
diff --git a/src/main/java/org/junit/internal/runners/model/EachTestNotifier.java b/src/main/java/org/junit/internal/runners/model/EachTestNotifier.java
index e094809fdf91..c5a0764e6a47 100644
--- a/src/main/java/org/junit/internal/runners/model/EachTestNotifier.java
+++ b/src/main/java/org/junit/internal/runners/model/EachTestNotifier.java
@@ -45,4 +45,27 @@ public void fireTestStarted() {
public void fireTestIgnored() {
notifier.fireTestIgnored(description);
}
+
+ /**
+ * Calls {@link RunNotifier#fireTestSuiteStarted(Description)}, passing the
+ * {@link Description} that was passed to the {@code EachTestNotifier} constructor.
+ * This should be called when a test suite is about to be started.
+ * @see RunNotifier#fireTestSuiteStarted(Description)
+ * @since 4.13
+ */
+ public void fireTestSuiteStarted() {
+ notifier.fireTestSuiteStarted(description);
+ }
+
+ /**
+ * Calls {@link RunNotifier#fireTestSuiteFinished(Description)}, passing the
+ * {@link Description} that was passed to the {@code EachTestNotifier} constructor.
+ * This should be called when a test suite has finished, whether the test suite succeeds
+ * or fails.
+ * @see RunNotifier#fireTestSuiteFinished(Description)
+ * @since 4.13
+ */
+ public void fireTestSuiteFinished() {
+ notifier.fireTestSuiteFinished(description);
+ }
}
\ No newline at end of file
diff --git a/src/main/java/org/junit/internal/runners/rules/ValidationError.java b/src/main/java/org/junit/internal/runners/rules/ValidationError.java
index d1af8ae7b744..31bd66019e09 100644
--- a/src/main/java/org/junit/internal/runners/rules/ValidationError.java
+++ b/src/main/java/org/junit/internal/runners/rules/ValidationError.java
@@ -5,6 +5,9 @@
import java.lang.annotation.Annotation;
class ValidationError extends Exception {
+
+ private static final long serialVersionUID = 3176511008672645574L;
+
public ValidationError(FrameworkMember> member, Class extends Annotation> annotation, String suffix) {
super(String.format("The @%s '%s' %s", annotation.getSimpleName(), member.getName(), suffix));
}
diff --git a/src/main/java/org/junit/internal/runners/statements/ExpectException.java b/src/main/java/org/junit/internal/runners/statements/ExpectException.java
index d0636bd78bbb..9a2a952858a3 100644
--- a/src/main/java/org/junit/internal/runners/statements/ExpectException.java
+++ b/src/main/java/org/junit/internal/runners/statements/ExpectException.java
@@ -19,7 +19,9 @@ public void evaluate() throws Exception {
next.evaluate();
complete = true;
} catch (AssumptionViolatedException e) {
- throw e;
+ if (!expected.isAssignableFrom(e.getClass())) {
+ throw e;
+ }
} catch (Throwable e) {
if (!expected.isAssignableFrom(e.getClass())) {
String message = "Unexpected exception, expected<"
diff --git a/src/main/java/org/junit/internal/runners/statements/FailOnTimeout.java b/src/main/java/org/junit/internal/runners/statements/FailOnTimeout.java
index 7f4f0d521b16..94c12e6e9770 100644
--- a/src/main/java/org/junit/internal/runners/statements/FailOnTimeout.java
+++ b/src/main/java/org/junit/internal/runners/statements/FailOnTimeout.java
@@ -1,8 +1,8 @@
package org.junit.internal.runners.statements;
-import java.lang.management.ManagementFactory;
-import java.lang.management.ThreadMXBean;
import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
@@ -10,6 +10,8 @@
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
+import org.junit.internal.management.ManagementFactory;
+import org.junit.internal.management.ThreadMXBean;
import org.junit.runners.model.MultipleFailureException;
import org.junit.runners.model.Statement;
import org.junit.runners.model.TestTimedOutException;
@@ -19,7 +21,6 @@ public class FailOnTimeout extends Statement {
private final TimeUnit timeUnit;
private final long timeout;
private final boolean lookForStuckThread;
- private volatile ThreadGroup threadGroup = null;
/**
* Returns a new builder for building an instance.
@@ -119,14 +120,28 @@ public FailOnTimeout build(Statement statement) {
public void evaluate() throws Throwable {
CallableStatement callable = new CallableStatement();
FutureTask task = new FutureTask(callable);
- threadGroup = new ThreadGroup("FailOnTimeoutGroup");
+ ThreadGroup threadGroup = new ThreadGroup("FailOnTimeoutGroup");
Thread thread = new Thread(threadGroup, task, "Time-limited test");
- thread.setDaemon(true);
- thread.start();
- callable.awaitStarted();
- Throwable throwable = getResult(task, thread);
- if (throwable != null) {
- throw throwable;
+ try {
+ thread.setDaemon(true);
+ thread.start();
+ callable.awaitStarted();
+ Throwable throwable = getResult(task, thread);
+ if (throwable != null) {
+ throw throwable;
+ }
+ } finally {
+ try {
+ thread.join(1);
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+ try {
+ threadGroup.destroy();
+ } catch (IllegalThreadStateException e) {
+ // If a thread from the group is still alive, the ThreadGroup cannot be destroyed.
+ // Swallow the exception to keep the same behavior prior to this change.
+ }
}
}
@@ -162,7 +177,7 @@ private Exception createTimeoutException(Thread thread) {
}
if (stuckThread != null) {
Exception stuckThreadException =
- new Exception ("Appears to be stuck in thread " +
+ new Exception("Appears to be stuck in thread " +
stuckThread.getName());
stuckThreadException.setStackTrace(getStackTrace(stuckThread));
return new MultipleFailureException(
@@ -197,11 +212,8 @@ private StackTraceElement[] getStackTrace(Thread thread) {
* to {@code mainThread}.
*/
private Thread getStuckThread(Thread mainThread) {
- if (threadGroup == null) {
- return null;
- }
- Thread[] threadsInGroup = getThreadArray(threadGroup);
- if (threadsInGroup == null) {
+ List threadsInGroup = getThreadsInGroup(mainThread.getThreadGroup());
+ if (threadsInGroup.isEmpty()) {
return null;
}
@@ -228,49 +240,27 @@ private Thread getStuckThread(Thread mainThread) {
* Returns all active threads belonging to a thread group.
* @param group The thread group.
* @return The active threads in the thread group. The result should be a
- * complete list of the active threads at some point in time. Returns {@code null}
+ * complete list of the active threads at some point in time. Returns an empty list
* if this cannot be determined, e.g. because new threads are being created at an
* extremely fast rate.
*/
- private Thread[] getThreadArray(ThreadGroup group) {
- final int count = group.activeCount(); // this is just an estimate
- int enumSize = Math.max(count * 2, 100);
- int enumCount;
- Thread[] threads;
- int loopCount = 0;
- while (true) {
- threads = new Thread[enumSize];
- enumCount = group.enumerate(threads);
- if (enumCount < enumSize) {
- break;
+ private List getThreadsInGroup(ThreadGroup group) {
+ final int activeThreadCount = group.activeCount(); // this is just an estimate
+ int threadArraySize = Math.max(activeThreadCount * 2, 100);
+ for (int loopCount = 0; loopCount < 5; loopCount++) {
+ Thread[] threads = new Thread[threadArraySize];
+ int enumCount = group.enumerate(threads);
+ if (enumCount < threadArraySize) {
+ return Arrays.asList(threads).subList(0, enumCount);
}
// if there are too many threads to fit into the array, enumerate's result
// is >= the array's length; therefore we can't trust that it returned all
// the threads. Try again.
- enumSize += 100;
- if (++loopCount >= 5) {
- return null;
- }
- // threads are proliferating too fast for us. Bail before we get into
- // trouble.
- }
- return copyThreads(threads, enumCount);
- }
-
- /**
- * Returns an array of the first {@code count} Threads in {@code threads}.
- * (Use instead of Arrays.copyOf to maintain compatibility with Java 1.5.)
- * @param threads The source array.
- * @param count The maximum length of the result array.
- * @return The first {@count} (at most) elements of {@code threads}.
- */
- private Thread[] copyThreads(Thread[] threads, int count) {
- int length = Math.min(count, threads.length);
- Thread[] result = new Thread[length];
- for (int i = 0; i < length; i++) {
- result[i] = threads[i];
+ threadArraySize += 100;
}
- return result;
+ // threads are proliferating too fast for us. Bail before we get into
+ // trouble.
+ return Collections.emptyList();
}
/**
@@ -278,7 +268,7 @@ private Thread[] copyThreads(Thread[] threads, int count) {
* @param thr The thread to query.
* @return The CPU time used by {@code thr}, or 0 if it cannot be determined.
*/
- private long cpuTime (Thread thr) {
+ private long cpuTime(Thread thr) {
ThreadMXBean mxBean = ManagementFactory.getThreadMXBean();
if (mxBean.isThreadCpuTimeSupported()) {
try {
diff --git a/src/main/java/org/junit/internal/runners/statements/RunAfters.java b/src/main/java/org/junit/internal/runners/statements/RunAfters.java
index 7512a7d61f40..5e56c3350ac1 100644
--- a/src/main/java/org/junit/internal/runners/statements/RunAfters.java
+++ b/src/main/java/org/junit/internal/runners/statements/RunAfters.java
@@ -30,7 +30,7 @@ public void evaluate() throws Throwable {
} finally {
for (FrameworkMethod each : afters) {
try {
- each.invokeExplosively(target);
+ invokeMethod(each);
} catch (Throwable e) {
errors.add(e);
}
@@ -38,4 +38,11 @@ public void evaluate() throws Throwable {
}
MultipleFailureException.assertEmpty(errors);
}
+
+ /**
+ * @since 4.13
+ */
+ protected void invokeMethod(FrameworkMethod method) throws Throwable {
+ method.invokeExplosively(target);
+ }
}
\ No newline at end of file
diff --git a/src/main/java/org/junit/internal/runners/statements/RunBefores.java b/src/main/java/org/junit/internal/runners/statements/RunBefores.java
index 238fbe7d07a5..bd835c772530 100644
--- a/src/main/java/org/junit/internal/runners/statements/RunBefores.java
+++ b/src/main/java/org/junit/internal/runners/statements/RunBefores.java
@@ -21,8 +21,15 @@ public RunBefores(Statement next, List befores, Object target)
@Override
public void evaluate() throws Throwable {
for (FrameworkMethod before : befores) {
- before.invokeExplosively(target);
+ invokeMethod(before);
}
next.evaluate();
}
+
+ /**
+ * @since 4.13
+ */
+ protected void invokeMethod(FrameworkMethod method) throws Throwable {
+ method.invokeExplosively(target);
+ }
}
\ No newline at end of file
diff --git a/src/main/java/org/junit/rules/DisableOnDebug.java b/src/main/java/org/junit/rules/DisableOnDebug.java
index afa6dee7d735..3bca103ed5a4 100644
--- a/src/main/java/org/junit/rules/DisableOnDebug.java
+++ b/src/main/java/org/junit/rules/DisableOnDebug.java
@@ -1,9 +1,9 @@
package org.junit.rules;
-import java.lang.management.ManagementFactory;
-import java.lang.management.RuntimeMXBean;
import java.util.List;
+import org.junit.internal.management.ManagementFactory;
+import org.junit.internal.management.RuntimeMXBean;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;
@@ -103,9 +103,7 @@ public Statement apply(Statement base, Description description) {
*/
private static boolean isDebugging(List arguments) {
for (final String argument : arguments) {
- if ("-Xdebug".equals(argument)) {
- return true;
- } else if (argument.startsWith("-agentlib:jdwp")) {
+ if ("-Xdebug".equals(argument) || argument.startsWith("-agentlib:jdwp")) {
return true;
}
}
diff --git a/src/main/java/org/junit/rules/ErrorCollector.java b/src/main/java/org/junit/rules/ErrorCollector.java
index 8c6600e882e4..9711e50358f7 100644
--- a/src/main/java/org/junit/rules/ErrorCollector.java
+++ b/src/main/java/org/junit/rules/ErrorCollector.java
@@ -1,11 +1,14 @@
package org.junit.rules;
import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertThrows;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
+import org.junit.function.ThrowingRunnable;
+import org.junit.internal.AssumptionViolatedException;
import org.hamcrest.Matcher;
import org.junit.runners.model.MultipleFailureException;
@@ -43,7 +46,16 @@ protected void verify() throws Throwable {
* Adds a Throwable to the table. Execution continues, but the test will fail at the end.
*/
public void addError(Throwable error) {
- errors.add(error);
+ if (error == null) {
+ throw new NullPointerException("Error cannot be null");
+ }
+ if (error instanceof AssumptionViolatedException) {
+ AssertionError e = new AssertionError(error.getMessage());
+ e.initCause(error);
+ errors.add(e);
+ } else {
+ errors.add(error);
+ }
}
/**
@@ -76,9 +88,33 @@ public Object call() throws Exception {
public T checkSucceeds(Callable callable) {
try {
return callable.call();
+ } catch (AssumptionViolatedException e) {
+ AssertionError error = new AssertionError("Callable threw AssumptionViolatedException");
+ error.initCause(e);
+ addError(error);
+ return null;
} catch (Throwable e) {
addError(e);
return null;
}
}
+
+ /**
+ * Adds a failure to the table if {@code runnable} does not throw an
+ * exception of type {@code expectedThrowable} when executed.
+ * Execution continues, but the test will fail at the end if the runnable
+ * does not throw an exception, or if it throws a different exception.
+ *
+ * @param expectedThrowable the expected type of the exception
+ * @param runnable a function that is expected to throw an exception when executed
+ * @since 4.13
+ */
+ public void checkThrows(Class extends Throwable> expectedThrowable, ThrowingRunnable runnable) {
+ try {
+ assertThrows(expectedThrowable, runnable);
+ } catch (AssertionError e) {
+ addError(e);
+ }
+ }
+
}
diff --git a/src/main/java/org/junit/rules/ExpectedException.java b/src/main/java/org/junit/rules/ExpectedException.java
index 4d61712803a0..431ad495b12f 100644
--- a/src/main/java/org/junit/rules/ExpectedException.java
+++ b/src/main/java/org/junit/rules/ExpectedException.java
@@ -7,7 +7,6 @@
import static org.junit.Assert.fail;
import static org.junit.internal.matchers.ThrowableCauseMatcher.hasCause;
import static org.junit.internal.matchers.ThrowableMessageMatcher.hasMessage;
-
import org.hamcrest.Matcher;
import org.hamcrest.StringDescription;
import org.junit.AssumptionViolatedException;
@@ -21,7 +20,7 @@
*
*
public class SimpleExpectedExceptionTest {
* @Rule
- * public ExpectedException thrown= ExpectedException.none();
+ * public ExpectedException thrown = ExpectedException.none();
*
* @Test
* public void throwsNothing() {
@@ -35,16 +34,19 @@
* }
* }
*
- *
- * You have to add the {@code ExpectedException} rule to your test.
+ *
You have to add the {@code ExpectedException} rule to your test.
* This doesn't affect your existing tests (see {@code throwsNothing()}).
- * After specifiying the type of the expected exception your test is
+ * After specifying the type of the expected exception your test is
* successful when such an exception is thrown and it fails if a
* different or no exception is thrown.
*
- *
- * Instead of specifying the exception's type you can characterize the
- * expected exception based on other criterias, too:
+ *
This rule does not perform any special magic to make execution continue
+ * as if the exception had not been thrown. So it is nearly always a mistake
+ * for a test method to have statements after the one that is expected to
+ * throw the exception.
+ *
+ *
Instead of specifying the exception's type you can characterize the
+ * expected exception based on other criteria, too:
*
*
*
The exception's message contains a specific text: {@link #expectMessage(String)}
@@ -53,8 +55,7 @@
*
The exception itself complies with a Hamcrest matcher: {@link #expect(Matcher)}
*
*
- *
- * You can combine any of the presented expect-methods. The test is
+ *
You can combine any of the presented expect-methods. The test is
* successful if all specifications are met.
*
@Test
* public void throwsException() {
@@ -63,9 +64,15 @@
* throw new NullPointerException("What happened?");
* }
*
+ *
It is recommended to set the {@link org.junit.Rule#order() order} of the
+ * {@code ExpectedException} to {@code Integer.MAX_VALUE} if it is used together
+ * with another rule that handles exceptions, e.g. {@link ErrorCollector}.
+ * Otherwise failing tests may be successful.
+ *
@Rule(order = Integer.MAX_VALUE)
+ * public ExpectedException thrown = ExpectedException.none();
+ *
*
AssumptionViolatedExceptions
- *
- * JUnit uses {@link AssumptionViolatedException}s for indicating that a test
+ *
JUnit uses {@link AssumptionViolatedException}s for indicating that a test
* provides no useful information. (See {@link org.junit.Assume} for more
* information.) You have to call {@code assume} methods before you set
* expectations of the {@code ExpectedException} rule. In this case the rule
@@ -80,8 +87,7 @@
*
*
AssertionErrors
*
- *
- * JUnit uses {@link AssertionError}s for indicating that a test is failing. You
+ *
JUnit uses {@link AssertionError}s for indicating that a test is failing. You
* have to call {@code assert} methods before you set expectations of the
* {@code ExpectedException} rule, if they should be handled by the framework.
* E.g. the following test fails because of the {@code assertTrue} statement.
@@ -93,8 +99,7 @@
* }
*
*
Missing Exceptions
- *
- * By default missing exceptions are reported with an error message
+ *
By default missing exceptions are reported with an error message
* like "Expected test to throw an instance of foo". You can configure a different
* message by means of {@link #reportMissingExceptionWithMessage(String)}. You
* can use a {@code %s} placeholder for the description of the expected
@@ -107,7 +112,13 @@ public class ExpectedException implements TestRule {
/**
* Returns a {@linkplain TestRule rule} that expects no exception to
* be thrown (identical to behavior without this rule).
+ *
+ * @deprecated Since 4.13
+ * {@link org.junit.Assert#assertThrows(Class, org.junit.function.ThrowingRunnable)
+ * Assert.assertThrows} can be used to verify that your code throws a specific
+ * exception.
*/
+ @Deprecated
public static ExpectedException none() {
return new ExpectedException();
}
@@ -222,10 +233,18 @@ public void expectMessage(Matcher matcher) {
* throw new IllegalArgumentException("What happened?", cause);
* }
*/
- public void expectCause(Matcher extends Throwable> expectedCause) {
+ public void expectCause(Matcher> expectedCause) {
expect(hasCause(expectedCause));
}
+ /**
+ * Check if any Exception is expected.
+ * @since 4.13
+ */
+ public final boolean isAnyExceptionExpected() {
+ return matcherBuilder.expectsThrowable();
+ }
+
private class ExpectedExceptionStatement extends Statement {
private final Statement next;
@@ -255,10 +274,6 @@ private void handleException(Throwable e) throws Throwable {
}
}
- private boolean isAnyExceptionExpected() {
- return matcherBuilder.expectsThrowable();
- }
-
private void failDueToMissingException() throws AssertionError {
fail(missingExceptionMessage());
}
diff --git a/src/main/java/org/junit/rules/ExternalResource.java b/src/main/java/org/junit/rules/ExternalResource.java
index 71ca287cce49..71fc84294308 100644
--- a/src/main/java/org/junit/rules/ExternalResource.java
+++ b/src/main/java/org/junit/rules/ExternalResource.java
@@ -1,6 +1,10 @@
package org.junit.rules;
+import java.util.ArrayList;
+import java.util.List;
+
import org.junit.runner.Description;
+import org.junit.runners.model.MultipleFailureException;
import org.junit.runners.model.Statement;
/**
@@ -44,11 +48,20 @@ private Statement statement(final Statement base) {
@Override
public void evaluate() throws Throwable {
before();
+
+ List errors = new ArrayList();
try {
base.evaluate();
+ } catch (Throwable t) {
+ errors.add(t);
} finally {
- after();
+ try {
+ after();
+ } catch (Throwable t) {
+ errors.add(t);
+ }
}
+ MultipleFailureException.assertEmpty(errors);
}
};
}
diff --git a/src/main/java/org/junit/rules/MethodRule.java b/src/main/java/org/junit/rules/MethodRule.java
index 823ee7812d00..94608f5a88f9 100644
--- a/src/main/java/org/junit/rules/MethodRule.java
+++ b/src/main/java/org/junit/rules/MethodRule.java
@@ -10,21 +10,9 @@
* {@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:
+ * an example of how this can be useful, see {@link TestWatchman}.
*
- *
- *
{@link ErrorCollector}: collect multiple errors in one test method
- *
{@link ExpectedException}: make flexible assertions about thrown exceptions
- *
{@link ExternalResource}: start and stop a server, for example
- *
{@link TemporaryFolder}: create fresh files, and delete after test
- *
{@link TestName}: remember the test name for use during the method
- *
{@link TestWatchman}: add logic at events during method execution
- *
{@link Timeout}: cause test to fail after a set time
- *
{@link Verifier}: fail test if object state ends up incorrect
- *
- *
- * Note that {@link MethodRule} has been replaced by {@link TestRule},
+ *
Note that {@link MethodRule} has been replaced by {@link TestRule},
* which has the added benefit of supporting class rules.
*
* @since 4.7
diff --git a/src/main/java/org/junit/rules/RuleChain.java b/src/main/java/org/junit/rules/RuleChain.java
index f43d8f5562d9..bf93aae1f318 100644
--- a/src/main/java/org/junit/rules/RuleChain.java
+++ b/src/main/java/org/junit/rules/RuleChain.java
@@ -4,26 +4,34 @@
import java.util.Collections;
import java.util.List;
+import org.junit.Rule;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;
/**
- * The RuleChain rule allows ordering of TestRules. You create a
+ * The {@code RuleChain} can be used for creating composite rules. You create a
* {@code RuleChain} with {@link #outerRule(TestRule)} and subsequent calls of
* {@link #around(TestRule)}:
*
*
+ * public class UseRuleChain {
+ * @Rule
+ * public final TestRule extendedLogging = CompositeRules.extendedLogging();
*
- * @Test
- * public void example() {
- * assertTrue(true);
- * }
+ * @Test
+ * public void example() {
+ * assertTrue(true);
+ * }
* }
*
*
@@ -38,6 +46,13 @@
* finished outer rule
*
*
+ * In older versions of JUnit (before 4.13) {@code RuleChain} was used for
+ * ordering rules. We recommend to not use it for this purpose anymore. You can
+ * use the attribute {@code order} of the annotation {@link Rule#order() Rule}
+ * or {@link org.junit.ClassRule#order() ClassRule} for ordering rules.
+ *
+ * @see org.junit.Rule#order()
+ * @see org.junit.ClassRule#order()
* @since 4.10
*/
public class RuleChain implements TestRule {
@@ -72,13 +87,17 @@ private RuleChain(List rules) {
}
/**
- * Create a new {@code RuleChain}, which encloses the {@code nextRule} with
+ * Create a new {@code RuleChain}, which encloses the given {@link TestRule} with
* the rules of the current {@code RuleChain}.
*
- * @param enclosedRule the rule to enclose.
+ * @param enclosedRule the rule to enclose; must not be {@code null}.
* @return a new {@code RuleChain}.
+ * @throws NullPointerException if the argument {@code enclosedRule} is {@code null}
*/
public RuleChain around(TestRule enclosedRule) {
+ if (enclosedRule == null) {
+ throw new NullPointerException("The enclosed rule must not be null");
+ }
List rulesOfNewChain = new ArrayList();
rulesOfNewChain.add(enclosedRule);
rulesOfNewChain.addAll(rulesStartingWithInnerMost);
@@ -89,9 +108,6 @@ public RuleChain around(TestRule enclosedRule) {
* {@inheritDoc}
*/
public Statement apply(Statement base, Description description) {
- for (TestRule each : rulesStartingWithInnerMost) {
- base = each.apply(base, description);
- }
- return base;
+ return new RunRules(base, rulesStartingWithInnerMost, description);
}
}
\ No newline at end of file
diff --git a/src/main/java/org/junit/rules/Stopwatch.java b/src/main/java/org/junit/rules/Stopwatch.java
index 5d34e7f11a85..6900a481cf11 100644
--- a/src/main/java/org/junit/rules/Stopwatch.java
+++ b/src/main/java/org/junit/rules/Stopwatch.java
@@ -76,7 +76,7 @@
* @author tibor17
* @since 4.12
*/
-public abstract class Stopwatch implements TestRule {
+public class Stopwatch implements TestRule {
private final Clock clock;
private volatile long startNanos;
private volatile long endNanos;
diff --git a/src/main/java/org/junit/rules/TemporaryFolder.java b/src/main/java/org/junit/rules/TemporaryFolder.java
index dc75c93bb115..a726c66e36aa 100644
--- a/src/main/java/org/junit/rules/TemporaryFolder.java
+++ b/src/main/java/org/junit/rules/TemporaryFolder.java
@@ -1,15 +1,20 @@
package org.junit.rules;
+import static org.junit.Assert.fail;
+
import java.io.File;
import java.io.IOException;
+import java.lang.reflect.Array;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
import org.junit.Rule;
/**
* The TemporaryFolder Rule allows creation of files and folders that should
* be deleted when the test method finishes (whether it passes or
- * fails). Whether the deletion is successful or not is not checked by this rule.
- * No exception will be thrown in case the deletion fails.
+ * fails).
+ * By default no exception will be thrown in case the deletion fails.
*
*
Example of usage:
*
@@ -26,18 +31,104 @@
* }
*
*
+ *
TemporaryFolder rule supports assured deletion mode, which
+ * will fail the test in case deletion fails with {@link AssertionError}.
+ *
+ *
Creating TemporaryFolder with assured deletion:
+ *
+ *
* @since 4.7
*/
public class TemporaryFolder extends ExternalResource {
private final File parentFolder;
+ private final boolean assureDeletion;
private File folder;
+ private static final int TEMP_DIR_ATTEMPTS = 10000;
+ private static final String TMP_PREFIX = "junit";
+
+ /**
+ * Create a temporary folder which uses system default temporary-file
+ * directory to create temporary resources.
+ */
public TemporaryFolder() {
- this(null);
+ this((File) null);
}
+ /**
+ * Create a temporary folder which uses the specified directory to create
+ * temporary resources.
+ *
+ * @param parentFolder folder where temporary resources will be created.
+ * If {@code null} then system default temporary-file directory is used.
+ */
public TemporaryFolder(File parentFolder) {
this.parentFolder = parentFolder;
+ this.assureDeletion = false;
+ }
+
+ /**
+ * Create a {@link TemporaryFolder} initialized with
+ * values from a builder.
+ */
+ protected TemporaryFolder(Builder builder) {
+ this.parentFolder = builder.parentFolder;
+ this.assureDeletion = builder.assureDeletion;
+ }
+
+ /**
+ * Returns a new builder for building an instance of {@link TemporaryFolder}.
+ *
+ * @since 4.13
+ */
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ /**
+ * Builds an instance of {@link TemporaryFolder}.
+ *
+ * @since 4.13
+ */
+ public static class Builder {
+ private File parentFolder;
+ private boolean assureDeletion;
+
+ protected Builder() {}
+
+ /**
+ * Specifies which folder to use for creating temporary resources.
+ * If {@code null} then system default temporary-file directory is
+ * used.
+ *
+ * @return this
+ */
+ public Builder parentFolder(File parentFolder) {
+ this.parentFolder = parentFolder;
+ return this;
+ }
+
+ /**
+ * Setting this flag assures that no resources are left undeleted. Failure
+ * to fulfill the assurance results in failure of tests with an
+ * {@link AssertionError}.
+ *
+ * @return this
+ */
+ public Builder assureDeletion() {
+ this.assureDeletion = true;
+ return this;
+ }
+
+ /**
+ * Builds a {@link TemporaryFolder} instance using the values in this builder.
+ */
+ public TemporaryFolder build() {
+ return new TemporaryFolder(this);
+ }
}
@Override
@@ -75,52 +166,63 @@ public File newFile(String fileName) throws IOException {
* Returns a new fresh file with a random name under the temporary folder.
*/
public File newFile() throws IOException {
- return File.createTempFile("junit", null, getRoot());
+ return File.createTempFile(TMP_PREFIX, null, getRoot());
}
/**
- * Returns a new fresh folder with the given name under the temporary
+ * Returns a new fresh folder with the given path under the temporary
* folder.
*/
- public File newFolder(String folder) throws IOException {
- return newFolder(new String[]{folder});
+ public File newFolder(String path) throws IOException {
+ return newFolder(new String[]{path});
}
/**
- * Returns a new fresh folder with the given name(s) under the temporary
- * folder.
+ * Returns a new fresh folder with the given paths under the temporary
+ * folder. For example, if you pass in the strings {@code "parent"} and {@code "child"}
+ * then a directory named {@code "parent"} will be created under the temporary folder
+ * and a directory named {@code "child"} will be created under the newly-created
+ * {@code "parent"} directory.
*/
- public File newFolder(String... folderNames) throws IOException {
- File file = getRoot();
- for (int i = 0; i < folderNames.length; i++) {
- String folderName = folderNames[i];
- validateFolderName(folderName);
- file = new File(file, folderName);
- if (!file.mkdir() && isLastElementInArray(i, folderNames)) {
- throw new IOException(
- "a folder with the name \'" + folderName + "\' already exists");
- }
+ public File newFolder(String... paths) throws IOException {
+ if (paths.length == 0) {
+ throw new IllegalArgumentException("must pass at least one path");
}
- return file;
- }
-
- /**
- * Validates if multiple path components were used while creating a folder.
- *
- * @param folderName
- * Name of the folder being created
- */
- private void validateFolderName(String folderName) throws IOException {
- File tempFile = new File(folderName);
- if (tempFile.getParent() != null) {
- String errorMsg = "Folder name cannot consist of multiple path components separated by a file separator."
- + " Please use newFolder('MyParentFolder','MyFolder') to create hierarchies of folders";
- throw new IOException(errorMsg);
+
+ /*
+ * Before checking if the paths are absolute paths, check if create() was ever called,
+ * and if it wasn't, throw IllegalStateException.
+ */
+ File root = getRoot();
+ for (String path : paths) {
+ if (new File(path).isAbsolute()) {
+ throw new IOException("folder path \'" + path + "\' is not a relative path");
+ }
}
- }
- private boolean isLastElementInArray(int index, String[] array) {
- return index == array.length - 1;
+ File relativePath = null;
+ File file = root;
+ boolean lastMkdirsCallSuccessful = true;
+ for (String path : paths) {
+ relativePath = new File(relativePath, path);
+ file = new File(root, relativePath.getPath());
+
+ lastMkdirsCallSuccessful = file.mkdirs();
+ if (!lastMkdirsCallSuccessful && !file.isDirectory()) {
+ if (file.exists()) {
+ throw new IOException(
+ "a file with the path \'" + relativePath.getPath() + "\' exists");
+ } else {
+ throw new IOException(
+ "could not create a folder with the path \'" + relativePath.getPath() + "\'");
+ }
+ }
+ }
+ if (!lastMkdirsCallSuccessful) {
+ throw new IOException(
+ "a folder with the path \'" + relativePath.getPath() + "\' already exists");
+ }
+ return file;
}
/**
@@ -130,11 +232,63 @@ public File newFolder() throws IOException {
return createTemporaryFolderIn(getRoot());
}
- private File createTemporaryFolderIn(File parentFolder) throws IOException {
- File createdFolder = File.createTempFile("junit", "", parentFolder);
- createdFolder.delete();
- createdFolder.mkdir();
- return createdFolder;
+ private static File createTemporaryFolderIn(File parentFolder) throws IOException {
+ try {
+ return createTemporaryFolderWithNioApi(parentFolder);
+ } catch (ClassNotFoundException ignore) {
+ // Fallback for Java 5 and 6
+ return createTemporaryFolderWithFileApi(parentFolder);
+ } catch (InvocationTargetException e) {
+ Throwable cause = e.getCause();
+ if (cause instanceof IOException) {
+ throw (IOException) cause;
+ }
+ if (cause instanceof RuntimeException) {
+ throw (RuntimeException) cause;
+ }
+ IOException exception = new IOException("Failed to create temporary folder in " + parentFolder);
+ exception.initCause(cause);
+ throw exception;
+ } catch (Exception e) {
+ throw new RuntimeException("Failed to create temporary folder in " + parentFolder, e);
+ }
+ }
+
+ private static File createTemporaryFolderWithNioApi(File parentFolder) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
+ Class> filesClass = Class.forName("java.nio.file.Files");
+ Object fileAttributeArray = Array.newInstance(Class.forName("java.nio.file.attribute.FileAttribute"), 0);
+ Class> pathClass = Class.forName("java.nio.file.Path");
+ Object tempDir;
+ if (parentFolder != null) {
+ Method createTempDirectoryMethod = filesClass.getDeclaredMethod("createTempDirectory", pathClass, String.class, fileAttributeArray.getClass());
+ Object parentPath = File.class.getDeclaredMethod("toPath").invoke(parentFolder);
+ tempDir = createTempDirectoryMethod.invoke(null, parentPath, TMP_PREFIX, fileAttributeArray);
+ } else {
+ Method createTempDirectoryMethod = filesClass.getDeclaredMethod("createTempDirectory", String.class, fileAttributeArray.getClass());
+ tempDir = createTempDirectoryMethod.invoke(null, TMP_PREFIX, fileAttributeArray);
+ }
+ return (File) pathClass.getDeclaredMethod("toFile").invoke(tempDir);
+ }
+
+ private static File createTemporaryFolderWithFileApi(File parentFolder) throws IOException {
+ File createdFolder = null;
+ for (int i = 0; i < TEMP_DIR_ATTEMPTS; ++i) {
+ // Use createTempFile to get a suitable folder name.
+ String suffix = ".tmp";
+ File tmpFile = File.createTempFile(TMP_PREFIX, suffix, parentFolder);
+ String tmpName = tmpFile.toString();
+ // Discard .tmp suffix of tmpName.
+ String folderName = tmpName.substring(0, tmpName.length() - suffix.length());
+ createdFolder = new File(folderName);
+ if (createdFolder.mkdir()) {
+ tmpFile.delete();
+ return createdFolder;
+ }
+ tmpFile.delete();
+ }
+ throw new IOException("Unable to create temporary directory in: "
+ + parentFolder.toString() + ". Tried " + TEMP_DIR_ATTEMPTS + " times. "
+ + "Last attempted to create: " + createdFolder.toString());
}
/**
@@ -150,21 +304,48 @@ public File getRoot() {
/**
* Delete all files and folders under the temporary folder. Usually not
- * called directly, since it is automatically applied by the {@link Rule}
+ * called directly, since it is automatically applied by the {@link Rule}.
+ *
+ * @throws AssertionError if unable to clean up resources
+ * and deletion of resources is assured.
*/
public void delete() {
- if (folder != null) {
- recursiveDelete(folder);
+ if (!tryDelete()) {
+ if (assureDeletion) {
+ fail("Unable to clean up temporary folder " + folder);
+ }
+ }
+ }
+
+ /**
+ * Tries to delete all files and folders under the temporary folder and
+ * returns whether deletion was successful or not.
+ *
+ * @return {@code true} if all resources are deleted successfully,
+ * {@code false} otherwise.
+ */
+ private boolean tryDelete() {
+ if (folder == null) {
+ return true;
}
+
+ return recursiveDelete(folder);
}
- private void recursiveDelete(File file) {
+ private boolean recursiveDelete(File file) {
+ // Try deleting file before assuming file is a directory
+ // to prevent following symbolic links.
+ if (file.delete()) {
+ return true;
+ }
File[] files = file.listFiles();
if (files != null) {
for (File each : files) {
- recursiveDelete(each);
+ if (!recursiveDelete(each)) {
+ return false;
+ }
}
}
- file.delete();
+ return file.delete();
}
}
diff --git a/src/main/java/org/junit/rules/TestName.java b/src/main/java/org/junit/rules/TestName.java
index bf726023bf68..e2ebc2ec97d1 100644
--- a/src/main/java/org/junit/rules/TestName.java
+++ b/src/main/java/org/junit/rules/TestName.java
@@ -25,7 +25,7 @@
* @since 4.7
*/
public class TestName extends TestWatcher {
- private String name;
+ private volatile String name;
@Override
protected void starting(Description d) {
diff --git a/src/main/java/org/junit/rules/TestWatcher.java b/src/main/java/org/junit/rules/TestWatcher.java
index 5492b6bc571b..a28514dd2b2f 100644
--- a/src/main/java/org/junit/rules/TestWatcher.java
+++ b/src/main/java/org/junit/rules/TestWatcher.java
@@ -4,6 +4,7 @@
import java.util.List;
import org.junit.AssumptionViolatedException;
+import org.junit.Rule;
import org.junit.runner.Description;
import org.junit.runners.model.MultipleFailureException;
import org.junit.runners.model.Statement;
@@ -17,7 +18,7 @@
* public static class WatchmanTest {
* private static String watchedLog;
*
- * @Rule
+ * @Rule(order = Integer.MIN_VALUE)
* public TestWatcher watchman= new TestWatcher() {
* @Override
* protected void failed(Throwable e, Description description) {
@@ -40,6 +41,11 @@
* }
* }
*
+ *
It is recommended to always set the {@link Rule#order() order} of the
+ * {@code TestWatcher} to {@code Integer.MIN_VALUE} so that it encloses all
+ * other rules. Otherwise it may see failed tests as successful and vice versa
+ * if some rule changes the result of a test (e.g. {@link ErrorCollector} or
+ * {@link ExpectedException}).
*
* @since 4.9
*/
@@ -54,7 +60,7 @@ public void evaluate() throws Throwable {
try {
base.evaluate();
succeededQuietly(description, errors);
- } catch (@SuppressWarnings("deprecation") org.junit.internal.AssumptionViolatedException e) {
+ } catch (org.junit.internal.AssumptionViolatedException e) {
errors.add(e);
skippedQuietly(e, description, errors);
} catch (Throwable e) {
@@ -87,7 +93,6 @@ private void failedQuietly(Throwable e, Description description,
}
}
- @SuppressWarnings("deprecation")
private void skippedQuietly(
org.junit.internal.AssumptionViolatedException e, Description description,
List errors) {
@@ -135,7 +140,6 @@ protected void failed(Throwable e, Description description) {
/**
* Invoked when a test is skipped due to a failed assumption.
*/
- @SuppressWarnings("deprecation")
protected void skipped(AssumptionViolatedException e, Description description) {
// For backwards compatibility with JUnit 4.11 and earlier, call the legacy version
org.junit.internal.AssumptionViolatedException asInternalException = e;
diff --git a/src/main/java/org/junit/rules/Timeout.java b/src/main/java/org/junit/rules/Timeout.java
index 45a5bc532050..334a9235cd26 100644
--- a/src/main/java/org/junit/rules/Timeout.java
+++ b/src/main/java/org/junit/rules/Timeout.java
@@ -12,7 +12,7 @@
* public static class HasGlobalLongTimeout {
*
* @Rule
- * public Timeout globalTimeout= new Timeout(20);
+ * public Timeout globalTimeout = Timeout.millis(20);
*
* @Test
* public void run1() throws InterruptedException {
@@ -84,7 +84,7 @@ public Timeout(long timeout, TimeUnit timeUnit) {
}
/**
- * Create a {@code Timeout} instance initialized with values form
+ * Create a {@code Timeout} instance initialized with values from
* a builder.
*
* @since 4.12
diff --git a/src/main/java/org/junit/runner/Computer.java b/src/main/java/org/junit/runner/Computer.java
index 8bb4b20c1565..18d0d31ce11f 100644
--- a/src/main/java/org/junit/runner/Computer.java
+++ b/src/main/java/org/junit/runner/Computer.java
@@ -30,7 +30,17 @@ public Runner getSuite(final RunnerBuilder builder,
public Runner runnerForClass(Class> testClass) throws Throwable {
return getRunner(builder, testClass);
}
- }, classes);
+ }, classes) {
+ @Override
+ protected String getName() {
+ /*
+ * #1320 The generated suite is not based on a real class so
+ * only a 'null' description can be generated from it. This name
+ * will be overridden here.
+ */
+ return "classes";
+ }
+ };
}
/**
diff --git a/src/main/java/org/junit/runner/Describable.java b/src/main/java/org/junit/runner/Describable.java
index 151414181b35..293fdb32a5c6 100644
--- a/src/main/java/org/junit/runner/Describable.java
+++ b/src/main/java/org/junit/runner/Describable.java
@@ -10,5 +10,5 @@ public interface Describable {
/**
* @return a {@link Description} showing the tests to be run by the receiver
*/
- public abstract Description getDescription();
+ Description getDescription();
}
\ No newline at end of file
diff --git a/src/main/java/org/junit/runner/Description.java b/src/main/java/org/junit/runner/Description.java
index fe47eacb537b..0846a1e9d31b 100644
--- a/src/main/java/org/junit/runner/Description.java
+++ b/src/main/java/org/junit/runner/Description.java
@@ -124,6 +124,17 @@ public static Description createSuiteDescription(Class> testClass) {
return new Description(testClass, testClass.getName(), testClass.getAnnotations());
}
+ /**
+ * Create a Description named after testClass
+ *
+ * @param testClass A not null {@link Class} containing tests
+ * @param annotations meta-data about the test, for downstream interpreters
+ * @return a Description of testClass
+ */
+ public static Description createSuiteDescription(Class> testClass, Annotation... annotations) {
+ return new Description(testClass, testClass.getName(), annotations);
+ }
+
/**
* Describes a Runner which runs no tests
*/
@@ -139,7 +150,7 @@ public static Description createSuiteDescription(Class> testClass) {
/*
* We have to use the f prefix until the next major release to ensure
* serialization compatibility.
- * See https://github.com/junit-team/junit/issues/976
+ * See https://github.com/junit-team/junit4/issues/976
*/
private final Collection fChildren = new ConcurrentLinkedQueue();
private final String fDisplayName;
diff --git a/src/main/java/org/junit/runner/FilterFactory.java b/src/main/java/org/junit/runner/FilterFactory.java
index 57b4eaa64795..e2bfb7358044 100644
--- a/src/main/java/org/junit/runner/FilterFactory.java
+++ b/src/main/java/org/junit/runner/FilterFactory.java
@@ -16,7 +16,8 @@ public interface FilterFactory {
/**
* Exception thrown if the {@link Filter} cannot be created.
*/
- public static class FilterNotCreatedException extends Exception {
+ @SuppressWarnings("serial")
+ class FilterNotCreatedException extends Exception {
public FilterNotCreatedException(Exception exception) {
super(exception.getMessage(), exception);
}
diff --git a/src/main/java/org/junit/runner/JUnitCommandLineParseResult.java b/src/main/java/org/junit/runner/JUnitCommandLineParseResult.java
index 434157cebbff..338340789ee8 100644
--- a/src/main/java/org/junit/runner/JUnitCommandLineParseResult.java
+++ b/src/main/java/org/junit/runner/JUnitCommandLineParseResult.java
@@ -85,13 +85,11 @@ String[] parseOptions(String... args) {
}
private String[] copyArray(String[] args, int from, int to) {
- ArrayList result = new ArrayList();
-
+ String[] result = new String[to - from];
for (int j = from; j != to; ++j) {
- result.add(args[j]);
+ result[j - from] = args[j];
}
-
- return result.toArray(new String[result.size()]);
+ return result;
}
void parseParameters(String[] args) {
diff --git a/src/main/java/org/junit/runner/OrderWith.java b/src/main/java/org/junit/runner/OrderWith.java
new file mode 100644
index 000000000000..e8470c9dd1c8
--- /dev/null
+++ b/src/main/java/org/junit/runner/OrderWith.java
@@ -0,0 +1,28 @@
+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;
+
+import org.junit.runner.manipulation.Ordering;
+import org.junit.validator.ValidateWith;
+
+/**
+ * When a test class is annotated with @OrderWith or extends a class annotated
+ * with @OrderWith, JUnit will order the tests in the test class (and child
+ * test classes, if any) using the ordering defined by the {@link Ordering} class.
+ *
+ * @since 4.13
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.TYPE)
+@Inherited
+@ValidateWith(OrderWithValidator.class)
+public @interface OrderWith {
+ /**
+ * Gets a class that extends {@link Ordering}. The class must have a public no-arg constructor.
+ */
+ Class extends Ordering.Factory> value();
+}
diff --git a/src/main/java/org/junit/runner/OrderWithValidator.java b/src/main/java/org/junit/runner/OrderWithValidator.java
new file mode 100644
index 000000000000..f8eab2523c16
--- /dev/null
+++ b/src/main/java/org/junit/runner/OrderWithValidator.java
@@ -0,0 +1,38 @@
+package org.junit.runner;
+
+import static java.util.Collections.emptyList;
+import static java.util.Collections.singletonList;
+
+import java.util.List;
+
+import org.junit.FixMethodOrder;
+import org.junit.runners.model.TestClass;
+import org.junit.validator.AnnotationValidator;
+
+/**
+ * Validates that there are no errors in the use of the {@code OrderWith}
+ * annotation. If there is, a {@code Throwable} object will be added to the list
+ * of errors.
+ *
+ * @since 4.13
+ */
+public final class OrderWithValidator extends AnnotationValidator {
+
+ /**
+ * Adds to {@code errors} a throwable for each problem detected. Looks for
+ * {@code FixMethodOrder} annotations.
+ *
+ * @param testClass that is being validated
+ * @return A list of exceptions detected
+ *
+ * @since 4.13
+ */
+ @Override
+ public List validateAnnotatedClass(TestClass testClass) {
+ if (testClass.getAnnotation(FixMethodOrder.class) != null) {
+ return singletonList(
+ new Exception("@FixMethodOrder cannot be combined with @OrderWith"));
+ }
+ return emptyList();
+ }
+}
diff --git a/src/main/java/org/junit/runner/Request.java b/src/main/java/org/junit/runner/Request.java
index 79c0f1ee15db..7b9a99003b47 100644
--- a/src/main/java/org/junit/runner/Request.java
+++ b/src/main/java/org/junit/runner/Request.java
@@ -5,9 +5,11 @@
import org.junit.internal.builders.AllDefaultPossibilitiesBuilder;
import org.junit.internal.requests.ClassRequest;
import org.junit.internal.requests.FilterRequest;
+import org.junit.internal.requests.OrderingRequest;
import org.junit.internal.requests.SortingRequest;
import org.junit.internal.runners.ErrorReportingRunner;
import org.junit.runner.manipulation.Filter;
+import org.junit.runner.manipulation.Ordering;
import org.junit.runners.model.InitializationError;
/**
@@ -71,12 +73,11 @@ public static Request classWithoutSuiteMethod(Class> clazz) {
*/
public static Request classes(Computer computer, Class>... classes) {
try {
- AllDefaultPossibilitiesBuilder builder = new AllDefaultPossibilitiesBuilder(true);
+ AllDefaultPossibilitiesBuilder builder = new AllDefaultPossibilitiesBuilder();
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");
+ return runner(new ErrorReportingRunner(e, classes));
}
}
@@ -132,13 +133,16 @@ public Request filterWith(Filter filter) {
}
/**
- * Returns a Request that only runs contains tests whose {@link Description}
- * equals desiredDescription
+ * Returns a Request that only runs tests whose {@link Description}
+ * matches the given description.
*
- * @param desiredDescription {@link Description} of those tests that should be run
+ *
Returns an empty {@code Request} if {@code desiredDescription} is not a single test and filters all but the single
+ * test if {@code desiredDescription} is a single test.
+ *
+ * @param desiredDescription {@code Description} of those tests that should be run
* @return the filtered Request
*/
- public Request filterWith(final Description desiredDescription) {
+ public Request filterWith(Description desiredDescription) {
return filterWith(Filter.matchMethodDescription(desiredDescription));
}
@@ -149,15 +153,15 @@ public Request filterWith(final Description desiredDescription) {
* For example, here is code to run a test suite in alphabetical order:
*
* private static Comparator<Description> forward() {
- * return new Comparator<Description>() {
- * public int compare(Description o1, Description o2) {
- * return o1.getDisplayName().compareTo(o2.getDisplayName());
- * }
- * };
+ * 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()));
+ * new JUnitCore().run(Request.aClass(AllTests.class).sortWith(forward()));
* }
*
*
@@ -167,4 +171,32 @@ public Request filterWith(final Description desiredDescription) {
public Request sortWith(Comparator comparator) {
return new SortingRequest(this, comparator);
}
+
+ /**
+ * Returns a Request whose Tests can be run in a certain order, defined by
+ * ordering
+ *
+ * For example, here is code to run a test suite in reverse order:
+ *
+ *
+ * @return a Request with ordered Tests
+ * @since 4.13
+ */
+ public Request orderWith(Ordering ordering) {
+ return new OrderingRequest(this, ordering);
+ }
}
diff --git a/src/main/java/org/junit/runner/Result.java b/src/main/java/org/junit/runner/Result.java
index 73ad059caefb..4b5f4a406238 100644
--- a/src/main/java/org/junit/runner/Result.java
+++ b/src/main/java/org/junit/runner/Result.java
@@ -28,6 +28,7 @@ public class Result implements Serializable {
ObjectStreamClass.lookup(SerializedForm.class).getFields();
private final AtomicInteger count;
private final AtomicInteger ignoreCount;
+ private final AtomicInteger assumptionFailureCount;
private final CopyOnWriteArrayList failures;
private final AtomicLong runTime;
private final AtomicLong startTime;
@@ -38,6 +39,7 @@ public class Result implements Serializable {
public Result() {
count = new AtomicInteger();
ignoreCount = new AtomicInteger();
+ assumptionFailureCount = new AtomicInteger();
failures = new CopyOnWriteArrayList();
runTime = new AtomicLong();
startTime = new AtomicLong();
@@ -46,34 +48,35 @@ public Result() {
private Result(SerializedForm serializedForm) {
count = serializedForm.fCount;
ignoreCount = serializedForm.fIgnoreCount;
+ assumptionFailureCount = serializedForm.assumptionFailureCount;
failures = new CopyOnWriteArrayList(serializedForm.fFailures);
runTime = new AtomicLong(serializedForm.fRunTime);
startTime = new AtomicLong(serializedForm.fStartTime);
}
/**
- * @return the number of tests run
+ * Returns the number of tests run
*/
public int getRunCount() {
return count.get();
}
/**
- * @return the number of tests that failed during the run
+ * Returns the number of tests that failed during the run
*/
public int getFailureCount() {
return failures.size();
}
/**
- * @return the number of milliseconds it took to run the entire suite to run
+ * Returns the number of milliseconds it took to run the entire suite to run
*/
public long getRunTime() {
return runTime.get();
}
/**
- * @return the {@link Failure}s describing tests that failed and the problems they encountered
+ * Returns the {@link Failure}s describing tests that failed and the problems they encountered
*/
public List getFailures() {
return failures;
@@ -86,6 +89,20 @@ public int getIgnoreCount() {
return ignoreCount.get();
}
+ /**
+ * Returns the number of tests skipped because of an assumption failure
+ *
+ * @throws UnsupportedOperationException if the result was serialized in a version before JUnit 4.13
+ * @since 4.13
+ */
+ public int getAssumptionFailureCount() {
+ if (assumptionFailureCount == null) {
+ throw new UnsupportedOperationException(
+ "Result was serialized from a version of JUnit that doesn't support this method");
+ }
+ return assumptionFailureCount.get();
+ }
+
/**
* @return true if all tests succeeded
*/
@@ -137,7 +154,7 @@ public void testIgnored(Description description) throws Exception {
@Override
public void testAssumptionFailure(Failure failure) {
- // do nothing: same as passing (for 4.5; may change in 4.6)
+ assumptionFailureCount.getAndIncrement();
}
}
@@ -156,6 +173,7 @@ private static class SerializedForm implements Serializable {
private static final long serialVersionUID = 1L;
private final AtomicInteger fCount;
private final AtomicInteger fIgnoreCount;
+ private final AtomicInteger assumptionFailureCount;
private final List fFailures;
private final long fRunTime;
private final long fStartTime;
@@ -163,6 +181,7 @@ private static class SerializedForm implements Serializable {
public SerializedForm(Result result) {
fCount = result.count;
fIgnoreCount = result.ignoreCount;
+ assumptionFailureCount = result.assumptionFailureCount;
fFailures = Collections.synchronizedList(new ArrayList(result.failures));
fRunTime = result.runTime.longValue();
fStartTime = result.startTime.longValue();
@@ -172,6 +191,7 @@ public SerializedForm(Result result) {
private SerializedForm(ObjectInputStream.GetField fields) throws IOException {
fCount = (AtomicInteger) fields.get("fCount", null);
fIgnoreCount = (AtomicInteger) fields.get("fIgnoreCount", null);
+ assumptionFailureCount = (AtomicInteger) fields.get("assumptionFailureCount", null);
fFailures = (List) fields.get("fFailures", null);
fRunTime = fields.get("fRunTime", 0L);
fStartTime = fields.get("fStartTime", 0L);
@@ -184,6 +204,7 @@ public void serialize(ObjectOutputStream s) throws IOException {
fields.put("fFailures", fFailures);
fields.put("fRunTime", fRunTime);
fields.put("fStartTime", fStartTime);
+ fields.put("assumptionFailureCount", assumptionFailureCount);
s.writeFields();
}
diff --git a/src/main/java/org/junit/runner/manipulation/Alphanumeric.java b/src/main/java/org/junit/runner/manipulation/Alphanumeric.java
new file mode 100644
index 000000000000..8388d21eb804
--- /dev/null
+++ b/src/main/java/org/junit/runner/manipulation/Alphanumeric.java
@@ -0,0 +1,27 @@
+package org.junit.runner.manipulation;
+
+import java.util.Comparator;
+
+import org.junit.runner.Description;
+
+/**
+ * A sorter that orders tests alphanumerically by test name.
+ *
+ * @since 4.13
+ */
+public final class Alphanumeric extends Sorter implements Ordering.Factory {
+
+ public Alphanumeric() {
+ super(COMPARATOR);
+ }
+
+ public Ordering create(Context context) {
+ return this;
+ }
+
+ private static final Comparator COMPARATOR = new Comparator() {
+ public int compare(Description o1, Description o2) {
+ return o1.getDisplayName().compareTo(o2.getDisplayName());
+ }
+ };
+}
diff --git a/src/main/java/org/junit/runner/manipulation/InvalidOrderingException.java b/src/main/java/org/junit/runner/manipulation/InvalidOrderingException.java
new file mode 100644
index 000000000000..d9d60f778b62
--- /dev/null
+++ b/src/main/java/org/junit/runner/manipulation/InvalidOrderingException.java
@@ -0,0 +1,21 @@
+package org.junit.runner.manipulation;
+
+/**
+ * Thrown when an ordering does something invalid (like remove or add children)
+ *
+ * @since 4.13
+ */
+public class InvalidOrderingException extends Exception {
+ private static final long serialVersionUID = 1L;
+
+ public InvalidOrderingException() {
+ }
+
+ public InvalidOrderingException(String message) {
+ super(message);
+ }
+
+ public InvalidOrderingException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/src/main/java/org/junit/runner/manipulation/Orderable.java b/src/main/java/org/junit/runner/manipulation/Orderable.java
new file mode 100644
index 000000000000..9a12a3bc1ef2
--- /dev/null
+++ b/src/main/java/org/junit/runner/manipulation/Orderable.java
@@ -0,0 +1,21 @@
+package org.junit.runner.manipulation;
+
+/**
+ * Interface for runners that allow ordering of tests.
+ *
+ *
Beware of using this interface to cope with order dependencies between tests.
+ * Tests that are isolated from each other are less expensive to maintain and
+ * can be run individually.
+ *
+ * @since 4.13
+ */
+public interface Orderable extends Sortable {
+
+ /**
+ * Orders the tests using orderer
+ *
+ * @throws InvalidOrderingException if orderer does something invalid (like remove or add
+ * children)
+ */
+ void order(Orderer orderer) throws InvalidOrderingException;
+}
diff --git a/src/main/java/org/junit/runner/manipulation/Orderer.java b/src/main/java/org/junit/runner/manipulation/Orderer.java
new file mode 100644
index 000000000000..eb1305437076
--- /dev/null
+++ b/src/main/java/org/junit/runner/manipulation/Orderer.java
@@ -0,0 +1,62 @@
+package org.junit.runner.manipulation;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.junit.runner.Description;
+
+/**
+ * Orders tests.
+ *
+ * @since 4.13
+ */
+public final class Orderer {
+ private final Ordering ordering;
+
+ Orderer(Ordering delegate) {
+ this.ordering = delegate;
+ }
+
+ /**
+ * Orders the descriptions.
+ *
+ * @return descriptions in order
+ */
+ public List order(Collection descriptions)
+ throws InvalidOrderingException {
+ List inOrder = ordering.orderItems(
+ Collections.unmodifiableCollection(descriptions));
+ if (!ordering.validateOrderingIsCorrect()) {
+ return inOrder;
+ }
+
+ Set uniqueDescriptions = new HashSet(descriptions);
+ if (!uniqueDescriptions.containsAll(inOrder)) {
+ throw new InvalidOrderingException("Ordering added items");
+ }
+ Set resultAsSet = new HashSet(inOrder);
+ if (resultAsSet.size() != inOrder.size()) {
+ throw new InvalidOrderingException("Ordering duplicated items");
+ } else if (!resultAsSet.containsAll(uniqueDescriptions)) {
+ throw new InvalidOrderingException("Ordering removed items");
+ }
+
+ return inOrder;
+ }
+
+ /**
+ * Order the tests in target.
+ *
+ * @throws InvalidOrderingException if ordering does something invalid (like remove or add
+ * children)
+ */
+ public void apply(Object target) throws InvalidOrderingException {
+ if (target instanceof Orderable) {
+ Orderable orderable = (Orderable) target;
+ orderable.order(this);
+ }
+ }
+}
diff --git a/src/main/java/org/junit/runner/manipulation/Ordering.java b/src/main/java/org/junit/runner/manipulation/Ordering.java
new file mode 100644
index 000000000000..0d0ce93780e6
--- /dev/null
+++ b/src/main/java/org/junit/runner/manipulation/Ordering.java
@@ -0,0 +1,172 @@
+package org.junit.runner.manipulation;
+
+import java.lang.reflect.Constructor;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Random;
+
+import org.junit.runner.Description;
+import org.junit.runner.OrderWith;
+
+/**
+ * Reorders tests. An {@code Ordering} can reverse the order of tests, sort the
+ * order or even shuffle the order.
+ *
+ *
In general you will not need to use a Ordering directly.
+ * Instead, use {@link org.junit.runner.Request#orderWith(Ordering)}.
+ *
+ * @since 4.13
+ */
+public abstract class Ordering {
+ private static final String CONSTRUCTOR_ERROR_FORMAT
+ = "Ordering class %s should have a public constructor with signature "
+ + "%s(Ordering.Context context)";
+
+ /**
+ * Creates an {@link Ordering} that shuffles the items using the given
+ * {@link Random} instance.
+ */
+ public static Ordering shuffledBy(final Random random) {
+ return new Ordering() {
+ @Override
+ boolean validateOrderingIsCorrect() {
+ return false;
+ }
+
+ @Override
+ protected List orderItems(Collection descriptions) {
+ List shuffled = new ArrayList(descriptions);
+ Collections.shuffle(shuffled, random);
+ return shuffled;
+ }
+ };
+ }
+
+ /**
+ * Creates an {@link Ordering} from the given factory class. The class must have a public no-arg
+ * constructor.
+ *
+ * @param factoryClass class to use to create the ordering
+ * @param annotatedTestClass test class that is annotated with {@link OrderWith}.
+ * @throws InvalidOrderingException if the instance could not be created
+ */
+ public static Ordering definedBy(
+ Class extends Ordering.Factory> factoryClass, Description annotatedTestClass)
+ throws InvalidOrderingException {
+ if (factoryClass == null) {
+ throw new NullPointerException("factoryClass cannot be null");
+ }
+ if (annotatedTestClass == null) {
+ throw new NullPointerException("annotatedTestClass cannot be null");
+ }
+
+ Ordering.Factory factory;
+ try {
+ Constructor extends Ordering.Factory> constructor = factoryClass.getConstructor();
+ factory = constructor.newInstance();
+ } catch (NoSuchMethodException e) {
+ throw new InvalidOrderingException(String.format(
+ CONSTRUCTOR_ERROR_FORMAT,
+ getClassName(factoryClass),
+ factoryClass.getSimpleName()));
+ } catch (Exception e) {
+ throw new InvalidOrderingException(
+ "Could not create ordering for " + annotatedTestClass, e);
+ }
+ return definedBy(factory, annotatedTestClass);
+ }
+
+ /**
+ * Creates an {@link Ordering} from the given factory.
+ *
+ * @param factory factory to use to create the ordering
+ * @param annotatedTestClass test class that is annotated with {@link OrderWith}.
+ * @throws InvalidOrderingException if the instance could not be created
+ */
+ public static Ordering definedBy(
+ Ordering.Factory factory, Description annotatedTestClass)
+ throws InvalidOrderingException {
+ if (factory == null) {
+ throw new NullPointerException("factory cannot be null");
+ }
+ if (annotatedTestClass == null) {
+ throw new NullPointerException("annotatedTestClass cannot be null");
+ }
+
+ return factory.create(new Ordering.Context(annotatedTestClass));
+ }
+
+ private static String getClassName(Class> clazz) {
+ String name = clazz.getCanonicalName();
+ if (name == null) {
+ return clazz.getName();
+ }
+ return name;
+ }
+
+ /**
+ * Order the tests in target using this ordering.
+ *
+ * @throws InvalidOrderingException if ordering does something invalid (like remove or add
+ * children)
+ */
+ public void apply(Object target) throws InvalidOrderingException {
+ /*
+ * Note that some subclasses of Ordering override apply(). The Sorter
+ * subclass of Ordering overrides apply() to apply the sort (this is
+ * done because sorting is more efficient than ordering).
+ */
+ if (target instanceof Orderable) {
+ Orderable orderable = (Orderable) target;
+ orderable.order(new Orderer(this));
+ }
+ }
+
+ /**
+ * Returns {@code true} if this ordering could produce invalid results (i.e.
+ * if it could add or remove values).
+ */
+ boolean validateOrderingIsCorrect() {
+ return true;
+ }
+
+ /**
+ * Implemented by sub-classes to order the descriptions.
+ *
+ * @return descriptions in order
+ */
+ protected abstract List orderItems(Collection descriptions);
+
+ /** Context about the ordering being applied. */
+ public static class Context {
+ private final Description description;
+
+ /**
+ * Gets the description for the top-level target being ordered.
+ */
+ public Description getTarget() {
+ return description;
+ }
+
+ private Context(Description description) {
+ this.description = description;
+ }
+ }
+
+ /**
+ * Factory for creating {@link Ordering} instances.
+ *
+ *
For a factory to be used with {@code @OrderWith} it needs to have a public no-arg
+ * constructor.
+ */
+ public interface Factory {
+ /**
+ * Creates an Ordering instance using the given context. Implementations
+ * of this method that do not need to use the context can return the
+ * same instance every time.
+ */
+ Ordering create(Context context);
+ }
+}
diff --git a/src/main/java/org/junit/runner/manipulation/Sortable.java b/src/main/java/org/junit/runner/manipulation/Sortable.java
index 9ac864c60a2d..0c59f330a371 100644
--- a/src/main/java/org/junit/runner/manipulation/Sortable.java
+++ b/src/main/java/org/junit/runner/manipulation/Sortable.java
@@ -15,6 +15,6 @@ public interface Sortable {
*
* @param sorter the {@link Sorter} to use for sorting the tests
*/
- public void sort(Sorter sorter);
+ void sort(Sorter sorter);
}
diff --git a/src/main/java/org/junit/runner/manipulation/Sorter.java b/src/main/java/org/junit/runner/manipulation/Sorter.java
index 20192d0c96e8..4b5274c31012 100644
--- a/src/main/java/org/junit/runner/manipulation/Sorter.java
+++ b/src/main/java/org/junit/runner/manipulation/Sorter.java
@@ -1,16 +1,21 @@
package org.junit.runner.manipulation;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
import java.util.Comparator;
+import java.util.List;
import org.junit.runner.Description;
/**
* A Sorter orders tests. In general you will not need
- * to use a Sorter directly. Instead, use {@link org.junit.runner.Request#sortWith(Comparator)}.
+ * to use a Sorter directly. Instead, use
+ * {@link org.junit.runner.Request#sortWith(Comparator)}.
*
* @since 4.0
*/
-public class Sorter implements Comparator {
+public class Sorter extends Ordering implements Comparator {
/**
* NULL is a Sorter that leaves elements in an undefined order
*/
@@ -27,17 +32,26 @@ public int compare(Description o1, Description o2) {
* to sort tests
*
* @param comparator the {@link Comparator} to use when sorting tests
+ * @since 4.0
*/
public Sorter(Comparator comparator) {
this.comparator = comparator;
}
/**
- * Sorts the test in runner using comparator
+ * Sorts the tests in target using comparator.
+ *
+ * @since 4.0
*/
- public void apply(Object object) {
- if (object instanceof Sortable) {
- Sortable sortable = (Sortable) object;
+ @Override
+ public void apply(Object target) {
+ /*
+ * Note that all runners that are Orderable are also Sortable (because
+ * Orderable extends Sortable). Sorting is more efficient than ordering,
+ * so we override the parent behavior so we sort instead.
+ */
+ if (target instanceof Sortable) {
+ Sortable sortable = (Sortable) target;
sortable.sort(this);
}
}
@@ -45,4 +59,32 @@ public void apply(Object object) {
public int compare(Description o1, Description o2) {
return comparator.compare(o1, o2);
}
+
+ /**
+ * {@inheritDoc}
+ *
+ * @since 4.13
+ */
+ @Override
+ protected final List orderItems(Collection descriptions) {
+ /*
+ * In practice, we will never get here--Sorters do their work in the
+ * compare() method--but the Liskov substitution principle demands that
+ * we obey the general contract of Orderable. Luckily, it's trivial to
+ * implement.
+ */
+ List sorted = new ArrayList(descriptions);
+ Collections.sort(sorted, this); // Note: it would be incorrect to pass in "comparator"
+ return sorted;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @since 4.13
+ */
+ @Override
+ boolean validateOrderingIsCorrect() {
+ return false;
+ }
}
diff --git a/src/main/java/org/junit/runner/notification/Failure.java b/src/main/java/org/junit/runner/notification/Failure.java
index c03b4c181407..4551302807b3 100644
--- a/src/main/java/org/junit/runner/notification/Failure.java
+++ b/src/main/java/org/junit/runner/notification/Failure.java
@@ -1,9 +1,8 @@
package org.junit.runner.notification;
-import java.io.PrintWriter;
import java.io.Serializable;
-import java.io.StringWriter;
+import org.junit.internal.Throwables;
import org.junit.runner.Description;
/**
@@ -21,7 +20,7 @@ public class Failure implements Serializable {
/*
* We have to use the f prefix until the next major release to ensure
* serialization compatibility.
- * See https://github.com/junit-team/junit/issues/976
+ * See https://github.com/junit-team/junit4/issues/976
*/
private final Description fDescription;
private final Throwable fThrownException;
@@ -65,15 +64,19 @@ public String toString() {
}
/**
- * Convenience method
- *
- * @return the printed form of the exception
+ * Gets the printed form of the exception and its stack trace.
*/
public String getTrace() {
- StringWriter stringWriter = new StringWriter();
- PrintWriter writer = new PrintWriter(stringWriter);
- getException().printStackTrace(writer);
- return stringWriter.toString();
+ return Throwables.getStacktrace(getException());
+ }
+
+ /**
+ * Gets a the printed form of the exception, with a trimmed version of the stack trace.
+ * This method will attempt to filter out frames of the stack trace that are below
+ * the test method call.
+ */
+ public String getTrimmedTrace() {
+ return Throwables.getTrimmedStackTrace(getException());
}
/**
diff --git a/src/main/java/org/junit/runner/notification/RunListener.java b/src/main/java/org/junit/runner/notification/RunListener.java
index db9d8c1697ea..d7cac003f1c6 100644
--- a/src/main/java/org/junit/runner/notification/RunListener.java
+++ b/src/main/java/org/junit/runner/notification/RunListener.java
@@ -69,6 +69,34 @@ public void testRunStarted(Description description) throws Exception {
public void testRunFinished(Result result) throws Exception {
}
+ /**
+ * Called when a test suite is about to be started. If this method is
+ * called for a given {@link Description}, then {@link #testSuiteFinished(Description)}
+ * will also be called for the same {@code Description}.
+ *
+ *
Note that not all runners will call this method, so runners should
+ * be prepared to handle {@link #testStarted(Description)} calls for tests
+ * where there was no corresponding {@code testSuiteStarted()} call for
+ * the parent {@code Description}.
+ *
+ * @param description the description of the test suite that is about to be run
+ * (generally a class name)
+ * @since 4.13
+ */
+ public void testSuiteStarted(Description description) throws Exception {
+ }
+
+ /**
+ * Called when a test suite has finished, whether the test suite succeeds or fails.
+ * This method will not be called for a given {@link Description} unless
+ * {@link #testSuiteStarted(Description)} was called for the same @code Description}.
+ *
+ * @param description the description of the test suite that just ran
+ * @since 4.13
+ */
+ public void testSuiteFinished(Description description) throws Exception {
+ }
+
/**
* Called when an atomic test is about to be started.
*
diff --git a/src/main/java/org/junit/runner/notification/RunNotifier.java b/src/main/java/org/junit/runner/notification/RunNotifier.java
index 6875f76939ea..8b686cdf287a 100644
--- a/src/main/java/org/junit/runner/notification/RunNotifier.java
+++ b/src/main/java/org/junit/runner/notification/RunNotifier.java
@@ -65,8 +65,8 @@ private abstract class SafeNotifier {
void run() {
int capacity = currentListeners.size();
- ArrayList safeListeners = new ArrayList(capacity);
- ArrayList failures = new ArrayList(capacity);
+ List safeListeners = new ArrayList(capacity);
+ List failures = new ArrayList(capacity);
for (RunListener listener : currentListeners) {
try {
notifyListener(listener);
@@ -105,6 +105,41 @@ protected void notifyListener(RunListener each) throws Exception {
}.run();
}
+ /**
+ * Invoke to tell listeners that a test suite is about to start. Runners are strongly
+ * encouraged--but not required--to call this method. If this method is called for
+ * a given {@link Description} then {@link #fireTestSuiteFinished(Description)} MUST
+ * be called for the same {@code Description}.
+ *
+ * @param description the description of the suite test (generally a class name)
+ * @since 4.13
+ */
+ public void fireTestSuiteStarted(final Description description) {
+ new SafeNotifier() {
+ @Override
+ protected void notifyListener(RunListener each) throws Exception {
+ each.testSuiteStarted(description);
+ }
+ }.run();
+ }
+
+ /**
+ * Invoke to tell listeners that a test suite is about to finish. Always invoke
+ * this method if you invoke {@link #fireTestSuiteStarted(Description)}
+ * as listeners are likely to expect them to come in pairs.
+ *
+ * @param description the description of the suite test (generally a class name)
+ * @since 4.13
+ */
+ public void fireTestSuiteFinished(final Description description) {
+ new SafeNotifier() {
+ @Override
+ protected void notifyListener(RunListener each) throws Exception {
+ each.testSuiteFinished(description);
+ }
+ }.run();
+ }
+
/**
* Invoke to tell listeners that an atomic test is about to start.
*
diff --git a/src/main/java/org/junit/runner/notification/SynchronizedRunListener.java b/src/main/java/org/junit/runner/notification/SynchronizedRunListener.java
index c53c1ee642c7..400fed8fcebf 100644
--- a/src/main/java/org/junit/runner/notification/SynchronizedRunListener.java
+++ b/src/main/java/org/junit/runner/notification/SynchronizedRunListener.java
@@ -10,7 +10,7 @@
*
This class synchronizes all listener calls on a RunNotifier instance. This is done because
* prior to JUnit 4.12, all listeners were called in a synchronized block in RunNotifier,
* so no two listeners were ever called concurrently. If we instead made the methods here
- * sychronized, clients that added multiple listeners that called common code might see
+ * synchronized, clients that added multiple listeners that called common code might see
* issues due to the reduced synchronization.
*
* @author Tibor Digana (tibor17)
@@ -43,6 +43,37 @@ public void testRunFinished(Result result) throws Exception {
}
}
+ /**
+ * {@inheritDoc}
+ *
+ * Synchronized decorator for {@link RunListener#testSuiteStarted(Description)}.
+ * @param description the description of the test suite that is about to be run
+ * (generally a class name).
+ * @throws Exception if any occurs.
+ * @since 4.13
+ */
+ @Override
+ public void testSuiteStarted(Description description) throws Exception {
+ synchronized (monitor) {
+ listener.testSuiteStarted(description);
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * Synchronized decorator for {@link RunListener#testSuiteFinished(Description)}.
+ * @param description the description of the test suite that just ran.
+ * @throws Exception
+ * @since 4.13
+ */
+ @Override
+ public void testSuiteFinished(Description description) throws Exception {
+ synchronized (monitor) {
+ listener.testSuiteFinished(description);
+ }
+ }
+
@Override
public void testStarted(Description description) throws Exception {
synchronized (monitor) {
diff --git a/src/main/java/org/junit/runners/BlockJUnit4ClassRunner.java b/src/main/java/org/junit/runners/BlockJUnit4ClassRunner.java
index 4d06199164e8..455341aa0cb9 100644
--- a/src/main/java/org/junit/runners/BlockJUnit4ClassRunner.java
+++ b/src/main/java/org/junit/runners/BlockJUnit4ClassRunner.java
@@ -3,8 +3,10 @@
import static org.junit.internal.runners.rules.RuleMemberValidator.RULE_METHOD_VALIDATOR;
import static org.junit.internal.runners.rules.RuleMemberValidator.RULE_VALIDATOR;
+import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;
import org.junit.After;
@@ -21,14 +23,18 @@
import org.junit.internal.runners.statements.RunAfters;
import org.junit.internal.runners.statements.RunBefores;
import org.junit.rules.MethodRule;
-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.FrameworkMember;
import org.junit.runners.model.FrameworkMethod;
import org.junit.runners.model.InitializationError;
+import org.junit.runners.model.MemberValueConsumer;
import org.junit.runners.model.MultipleFailureException;
import org.junit.runners.model.Statement;
+import org.junit.runners.model.TestClass;
+import org.junit.validator.PublicClassValidator;
+import org.junit.validator.TestClassValidator;
/**
* Implements the JUnit 4 standard test case class model, as defined by the
@@ -55,14 +61,27 @@
* @since 4.5
*/
public class BlockJUnit4ClassRunner extends ParentRunner {
- private final ConcurrentHashMap methodDescriptions = new ConcurrentHashMap();
+ private static TestClassValidator PUBLIC_CLASS_VALIDATOR = new PublicClassValidator();
+
+ private final ConcurrentMap methodDescriptions = new ConcurrentHashMap();
+
+ /**
+ * Creates a BlockJUnit4ClassRunner to run {@code testClass}
+ *
+ * @throws InitializationError if the test class is malformed.
+ */
+ public BlockJUnit4ClassRunner(Class> testClass) throws InitializationError {
+ super(testClass);
+ }
+
/**
- * Creates a BlockJUnit4ClassRunner to run {@code klass}
+ * Creates a BlockJUnit4ClassRunner to run {@code testClass}.
*
* @throws InitializationError if the test class is malformed.
+ * @since 4.13
*/
- public BlockJUnit4ClassRunner(Class> klass) throws InitializationError {
- super(klass);
+ protected BlockJUnit4ClassRunner(TestClass testClass) throws InitializationError {
+ super(testClass);
}
//
@@ -75,10 +94,16 @@ protected void runChild(final FrameworkMethod method, RunNotifier notifier) {
if (isIgnored(method)) {
notifier.fireTestIgnored(description);
} else {
- runLeaf(methodBlock(method), description, notifier);
+ Statement statement = new Statement() {
+ @Override
+ public void evaluate() throws Throwable {
+ methodBlock(method).evaluate();
+ }
+ };
+ runLeaf(statement, description, notifier);
}
}
-
+
/**
* Evaluates whether {@link FrameworkMethod}s are ignored based on the
* {@link Ignore} annotation.
@@ -123,6 +148,7 @@ protected List computeTestMethods() {
protected void collectInitializationErrors(List errors) {
super.collectInitializationErrors(errors);
+ validatePublicConstructor(errors);
validateNoNonStaticInnerClass(errors);
validateConstructor(errors);
validateInstanceMethods(errors);
@@ -130,6 +156,12 @@ protected void collectInitializationErrors(List errors) {
validateMethods(errors);
}
+ private void validatePublicConstructor(List errors) {
+ if (getTestClass().getJavaClass() != null) {
+ errors.addAll(PUBLIC_CLASS_VALIDATOR.validateTestClass(getTestClass()));
+ }
+ }
+
protected void validateNoNonStaticInnerClass(List errors) {
if (getTestClass().isANonStaticInnerClass()) {
String gripe = "The inner class " + getTestClass().getName()
@@ -180,6 +212,7 @@ private boolean hasOneConstructor() {
* 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
*/
@Deprecated
protected void validateInstanceMethods(List errors) {
@@ -187,7 +220,7 @@ protected void validateInstanceMethods(List errors) {
validatePublicVoidNoArgMethods(Before.class, false, errors);
validateTestMethods(errors);
- if (computeTestMethods().size() == 0) {
+ if (computeTestMethods().isEmpty()) {
errors.add(new Exception("No runnable methods"));
}
}
@@ -217,6 +250,16 @@ protected Object createTest() throws Exception {
return getTestClass().getOnlyConstructor().newInstance();
}
+ /**
+ * Returns a new fixture to run a particular test {@code method} against.
+ * Default implementation executes the no-argument {@link #createTest()} method.
+ *
+ * @since 4.13
+ */
+ protected Object createTest(FrameworkMethod method) throws Exception {
+ return createTest();
+ }
+
/**
* Returns the name that describes {@code method} for {@link Description}s.
* Default implementation is the method's name
@@ -232,10 +275,10 @@ protected String testName(FrameworkMethod method) {
* Here is an outline of the default implementation:
*
*
- *
Invoke {@code method} on the result of {@code createTest()}, and
+ *
Invoke {@code method} on the result of {@link #createTest(org.junit.runners.model.FrameworkMethod)}, and
* throw any exceptions thrown by either operation.
- *
HOWEVER, if {@code method}'s {@code @Test} annotation has the {@code
- * expecting} attribute, return normally only if the previous step threw an
+ *
HOWEVER, if {@code method}'s {@code @Test} annotation has the {@link Test#expected()}
+ * attribute, return normally only if the previous step threw an
* exception of the correct type, and throw an exception otherwise.
*
HOWEVER, if {@code method}'s {@code @Test} annotation has the {@code
* timeout} attribute, throw an exception if the previous step takes more
@@ -257,13 +300,13 @@ protected String testName(FrameworkMethod method) {
* This can be overridden in subclasses, either by overriding this method,
* or the implementations creating each sub-statement.
*/
- protected Statement methodBlock(FrameworkMethod method) {
+ protected Statement methodBlock(final FrameworkMethod method) {
Object test;
try {
test = new ReflectiveCallable() {
@Override
protected Object runReflectiveCall() throws Throwable {
- return createTest();
+ return createTest(method);
}
}.run();
} catch (Throwable e) {
@@ -276,6 +319,7 @@ protected Object runReflectiveCall() throws Throwable {
statement = withBefores(method, test, statement);
statement = withAfters(method, test, statement);
statement = withRules(method, test, statement);
+ statement = withInterruptIsolation(statement);
return statement;
}
@@ -292,21 +336,22 @@ protected Statement methodInvoker(FrameworkMethod method, Object test) {
/**
* Returns a {@link Statement}: if {@code method}'s {@code @Test} annotation
- * has the {@code expecting} attribute, return normally only if {@code next}
+ * has the {@link Test#expected()} attribute, return normally only if {@code next}
* throws an exception of the correct type, and throw an exception
* otherwise.
*/
protected Statement possiblyExpectingExceptions(FrameworkMethod method,
Object test, Statement next) {
Test annotation = method.getAnnotation(Test.class);
- return expectsException(annotation) ? new ExpectException(next,
- getExpectedException(annotation)) : next;
+ Class extends Throwable> expectedExceptionClass = getExpectedException(annotation);
+ return expectedExceptionClass != null ? new ExpectException(next, expectedExceptionClass) : 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
*/
@Deprecated
protected Statement withPotentialTimeout(FrameworkMethod method,
@@ -348,28 +393,23 @@ protected Statement withAfters(FrameworkMethod method, Object target,
target);
}
- private Statement withRules(FrameworkMethod method, Object target,
- Statement statement) {
- List testRules = getTestRules(target);
- Statement result = statement;
- result = withMethodRules(method, testRules, target, result);
- result = withTestRules(method, testRules, result);
-
- return result;
- }
-
- private Statement withMethodRules(FrameworkMethod method, List testRules,
- Object target, Statement result) {
- for (org.junit.rules.MethodRule each : getMethodRules(target)) {
- if (!testRules.contains(each)) {
- result = each.apply(result, method, target);
+ private Statement withRules(FrameworkMethod method, Object target, Statement statement) {
+ RuleContainer ruleContainer = new RuleContainer();
+ CURRENT_RULE_CONTAINER.set(ruleContainer);
+ try {
+ List testRules = getTestRules(target);
+ for (MethodRule each : rules(target)) {
+ if (!(each instanceof TestRule && testRules.contains(each))) {
+ ruleContainer.add(each);
+ }
}
+ for (TestRule rule : testRules) {
+ ruleContainer.add(rule);
+ }
+ } finally {
+ CURRENT_RULE_CONTAINER.remove();
}
- return result;
- }
-
- private List getMethodRules(Object target) {
- return rules(target);
+ return ruleContainer.apply(method, describeChild(method), target, statement);
}
/**
@@ -378,27 +418,12 @@ private List getMethodRules(Object target) {
* test
*/
protected List rules(Object target) {
- List rules = getTestClass().getAnnotatedMethodValues(target,
- Rule.class, MethodRule.class);
-
- rules.addAll(getTestClass().getAnnotatedFieldValues(target,
- Rule.class, MethodRule.class));
-
- return rules;
- }
-
- /**
- * Returns a {@link Statement}: apply all non-static 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, List testRules,
- Statement statement) {
- return testRules.isEmpty() ? statement :
- new RunRules(statement, testRules, describeChild(method));
+ RuleCollector collector = new RuleCollector();
+ getTestClass().collectAnnotatedMethodValues(target, Rule.class, MethodRule.class,
+ collector);
+ getTestClass().collectAnnotatedFieldValues(target, Rule.class, MethodRule.class,
+ collector);
+ return collector.result;
}
/**
@@ -407,13 +432,10 @@ private Statement withTestRules(FrameworkMethod method, List testRules
* test
*/
protected List getTestRules(Object target) {
- List result = getTestClass().getAnnotatedMethodValues(target,
- Rule.class, TestRule.class);
-
- result.addAll(getTestClass().getAnnotatedFieldValues(target,
- Rule.class, TestRule.class));
-
- return result;
+ RuleCollector collector = new RuleCollector();
+ getTestClass().collectAnnotatedMethodValues(target, Rule.class, TestRule.class, collector);
+ getTestClass().collectAnnotatedFieldValues(target, Rule.class, TestRule.class, collector);
+ return collector.result;
}
private Class extends Throwable> getExpectedException(Test annotation) {
@@ -424,14 +446,28 @@ private Class extends Throwable> getExpectedException(Test annotation) {
}
}
- private boolean expectsException(Test annotation) {
- return getExpectedException(annotation) != null;
- }
-
private long getTimeout(Test annotation) {
if (annotation == null) {
return 0;
}
return annotation.timeout();
}
+
+ private static final ThreadLocal CURRENT_RULE_CONTAINER =
+ new ThreadLocal();
+
+ private static class RuleCollector implements MemberValueConsumer {
+ final List result = new ArrayList();
+
+ public void accept(FrameworkMember> member, T value) {
+ Rule rule = member.getAnnotation(Rule.class);
+ if (rule != null) {
+ RuleContainer container = CURRENT_RULE_CONTAINER.get();
+ if (container != null) {
+ container.setOrder(value, rule.order());
+ }
+ }
+ result.add(value);
+ }
+ }
}
diff --git a/src/main/java/org/junit/runners/JUnit4.java b/src/main/java/org/junit/runners/JUnit4.java
index 6ba28c2fd431..28eafb3a1050 100644
--- a/src/main/java/org/junit/runners/JUnit4.java
+++ b/src/main/java/org/junit/runners/JUnit4.java
@@ -1,6 +1,7 @@
package org.junit.runners;
import org.junit.runners.model.InitializationError;
+import org.junit.runners.model.TestClass;
/**
* Aliases the current default JUnit 4 class runner, for future-proofing. If
@@ -19,6 +20,6 @@ public final class JUnit4 extends BlockJUnit4ClassRunner {
* Constructs a new instance of the default runner
*/
public JUnit4(Class> klass) throws InitializationError {
- super(klass);
+ super(new TestClass(klass));
}
}
diff --git a/src/main/java/org/junit/runners/Parameterized.java b/src/main/java/org/junit/runners/Parameterized.java
index 829c8f0f15af..d11b66a2d0c3 100644
--- a/src/main/java/org/junit/runners/Parameterized.java
+++ b/src/main/java/org/junit/runners/Parameterized.java
@@ -1,5 +1,6 @@
package org.junit.runners;
+import java.lang.annotation.Annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
@@ -8,12 +9,18 @@
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collection;
import java.util.Collections;
import java.util.List;
+import org.junit.internal.AssumptionViolatedException;
+import org.junit.runner.Description;
+import org.junit.runner.Result;
import org.junit.runner.Runner;
+import org.junit.runner.notification.Failure;
+import org.junit.runner.notification.RunNotifier;
import org.junit.runners.model.FrameworkMethod;
-import org.junit.runners.model.InitializationError;
+import org.junit.runners.model.InvalidTestClassError;
import org.junit.runners.model.TestClass;
import org.junit.runners.parameterized.BlockJUnit4ClassRunnerWithParametersFactory;
import org.junit.runners.parameterized.ParametersRunnerFactory;
@@ -24,34 +31,37 @@
* When running a parameterized test class, instances are created for the
* cross-product of the test methods and the test data elements.
*
- * For example, to test a Fibonacci function, write:
+ * For example, to test the + operator, write:
*
- * Each instance of FibonacciTest will be constructed using the
- * two-argument constructor and the data values in the
+ * Each instance of AdditionTest will be constructed using the
+ * three-argument constructor and the data values in the
* @Parameters method.
*
* In order that you can easily identify the individual tests, you may provide a
@@ -69,33 +79,36 @@
*
*
* In the example given above, the Parameterized runner creates
- * names like [1: fib(3)=2]. If you don't use the name parameter,
+ * names like [2: 3 + 2 = 5]. If you don't use the name parameter,
* then the current parameter index is used as name.
*
- * Each instance of FibonacciTest will be constructed with the default constructor
+ * Each instance of AdditionTest will be constructed with the default constructor
* and fields annotated by @Parameter will be initialized
* with the data values in the @Parameters method.
*
@@ -105,8 +118,7 @@
*
Executing code before/after executing tests for specific parameters
+ *
+ * If your test needs to perform some preparation or cleanup based on the
+ * parameters, this can be done by adding public static methods annotated with
+ * {@code @BeforeParam}/{@code @AfterParam}. Such methods should either have no
+ * parameters or the same parameters as the test.
+ *
* By default the {@code Parameterized} runner creates a slightly modified
@@ -141,7 +166,7 @@
* The factory must have a public zero-arg constructor.
*
*
- * public class YourRunnerFactory implements ParameterizedRunnerFactory {
+ * public class YourRunnerFactory implements ParametersRunnerFactory {
* public Runner createRunnerForTestWithParameters(TestWithParameters test)
* throws InitializationError {
* return YourRunner(test);
@@ -160,6 +185,21 @@
* }
*
*
+ *
Avoid creating parameters
+ *
With {@link org.junit.Assume assumptions} you can dynamically skip tests.
+ * Assumptions are also supported by the @Parameters method.
+ * Creating parameters is stopped when the assumption fails and none of the
+ * tests in the test class is executed. JUnit reports a
+ * {@link Result#getAssumptionFailureCount() single assumption failure} for the
+ * whole test class in this case.
+ *