diff --git a/dev-support/hbasetests.sh b/dev-support/hbasetests.sh index 55cd0babcca4..0213219551a1 100755 --- a/dev-support/hbasetests.sh +++ b/dev-support/hbasetests.sh @@ -210,13 +210,13 @@ do isLarge=0 # determine the category of the test by greping into the source code - isMedium=`grep "@Category" $testFile | grep "MediumTests.class" | wc -l` + isMedium=$(grep -Ec '@(org\.junit\.jupiter\.api\.)?Tag\(MediumTests\.TAG\)' "$testFile") if (test $isMedium -eq 0) then - isLarge=`grep "@Category" $testFile | grep "LargeTests.class" | wc -l` + isLarge=$(grep -Ec '@(org\.junit\.jupiter\.api\.)?Tag\(LargeTests\.TAG\)' "$testFile") if (test $isLarge -eq 0) then - isSmall=`grep "@Category" $testFile | grep "SmallTests.class" | wc -l` + isSmall=$(grep -Ec '@(org\.junit\.jupiter\.api\.)?Tag\(SmallTests\.TAG\)' "$testFile") if (test $isSmall -eq 0) then echo "$testName is not categorized, so it won't be tested" diff --git a/hbase-annotations/src/test/java/org/apache/hadoop/hbase/testclassification/IntegrationTests.java b/hbase-annotations/src/test/java/org/apache/hadoop/hbase/testclassification/IntegrationTests.java index 0003cd1db511..f4a628ac8417 100644 --- a/hbase-annotations/src/test/java/org/apache/hadoop/hbase/testclassification/IntegrationTests.java +++ b/hbase-annotations/src/test/java/org/apache/hadoop/hbase/testclassification/IntegrationTests.java @@ -27,7 +27,7 @@ *
  • Should not be run in parallel of other integration tests
  • * * Integration / System tests should have a class name starting with "IntegrationTest", and should - * be annotated with @Category(IntegrationTests.class). Integration tests can be run using the + * be annotated with @Tag(IntegrationTests.TAG). Integration tests can be run using the * IntegrationTestsDriver class or from mvn verify. * @see SmallTests * @see MediumTests diff --git a/hbase-common/src/test/java/org/apache/hadoop/hbase/logging/TestJul2Slf4j.java b/hbase-common/src/test/java/org/apache/hadoop/hbase/logging/TestJul2Slf4j.java index 443d4a4f00e5..a32bd004644b 100644 --- a/hbase-common/src/test/java/org/apache/hadoop/hbase/logging/TestJul2Slf4j.java +++ b/hbase-common/src/test/java/org/apache/hadoop/hbase/logging/TestJul2Slf4j.java @@ -37,8 +37,8 @@ import org.mockito.stubbing.Answer; /** - * This should be in the hbase-logging module but the {@link HBaseClassTestRule} is in hbase-common - * so we can only put the class in hbase-common module for now... + * This should be in the hbase-logging module but hbase-logging cannot depend on hbase-common, so + * the class stays in hbase-common. */ @Tag(MiscTests.TAG) @Tag(SmallTests.TAG) diff --git a/hbase-common/src/test/java/org/apache/hadoop/hbase/logging/TestLog4jUtils.java b/hbase-common/src/test/java/org/apache/hadoop/hbase/logging/TestLog4jUtils.java index 00cba91e075f..57b31c5425c6 100644 --- a/hbase-common/src/test/java/org/apache/hadoop/hbase/logging/TestLog4jUtils.java +++ b/hbase-common/src/test/java/org/apache/hadoop/hbase/logging/TestLog4jUtils.java @@ -27,8 +27,8 @@ import org.junit.jupiter.api.Test; /** - * This should be in the hbase-logging module but the {@link HBaseClassTestRule} is in hbase-common - * so we can only put the class in hbase-common module for now... + * This should be in the hbase-logging module but hbase-logging cannot depend on hbase-common, so + * the class stays in hbase-common. */ @Tag(MiscTests.TAG) @Tag(SmallTests.TAG) diff --git a/hbase-it/pom.xml b/hbase-it/pom.xml index 137987ce6c95..ab5f29ec2868 100644 --- a/hbase-it/pom.xml +++ b/hbase-it/pom.xml @@ -257,13 +257,6 @@ false false - - - org.apache.maven.surefire - surefire-junit4 - ${surefire.version} - - integration-test diff --git a/hbase-it/src/test/java/org/apache/hadoop/hbase/IntegrationTestsDriver.java b/hbase-it/src/test/java/org/apache/hadoop/hbase/IntegrationTestsDriver.java index b381a9535ff8..c34aae5bf57c 100644 --- a/hbase-it/src/test/java/org/apache/hadoop/hbase/IntegrationTestsDriver.java +++ b/hbase-it/src/test/java/org/apache/hadoop/hbase/IntegrationTestsDriver.java @@ -94,7 +94,7 @@ protected void processOptions(CommandLine cmd) { } /** - * Returns test classes annotated with @Category(IntegrationTests.class), according to the filter + * Returns test classes annotated with @Tag(IntegrationTests.TAG), according to the filter * specific on the command line (if any). */ private Class[] findIntegrationTestClasses() diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/master/TestMasterShutdown.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/master/TestMasterShutdown.java index 42cb33643bab..292268cf7b4f 100644 --- a/hbase-server/src/test/java/org/apache/hadoop/hbase/master/TestMasterShutdown.java +++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/master/TestMasterShutdown.java @@ -55,8 +55,8 @@ public class TestMasterShutdown { @BeforeEach public void shutdownCluster() throws IOException { if (htu != null) { - // an extra check in case the test cluster was not terminated after HBaseClassTestRule's - // Timeout interrupted the test thread. + // an extra check in case the test cluster was not terminated after HBaseJupiterExtension's + // timeout interrupted the test thread. LOG.warn("found non-null TestingUtility -- previous test did not terminate cleanly."); htu.shutdownMiniCluster(); } diff --git a/hbase-website/app/pages/_docs/docs/_mdx/(multi-page)/building-and-developing/tests.mdx b/hbase-website/app/pages/_docs/docs/_mdx/(multi-page)/building-and-developing/tests.mdx index 52b143bf6a53..addaf058f97a 100644 --- a/hbase-website/app/pages/_docs/docs/_mdx/(multi-page)/building-and-developing/tests.mdx +++ b/hbase-website/app/pages/_docs/docs/_mdx/(multi-page)/building-and-developing/tests.mdx @@ -87,18 +87,15 @@ Also, keep in mind that if you are running tests in the `hbase-server` module yo ## Unit Tests [#building-and-developing-unit-tests] -Apache HBase unit tests must carry a Category annotation and -as of `hbase-2.0.0`, must be stamped with the HBase `ClassRule`. -Here is an example of what a Test Class looks like with a -Category and ClassRule included: +Apache HBase runs its unit tests with [JUnit 5](https://docs.junit.org/5.13.4/user-guide/). Every unit +test must carry a sizing tag, applied with the JUnit 5 `@Tag` annotation. Here is an +example of what a Test Class looks like: ```java ... -@Category(SmallTests.class) +@Tag(RegionServerTests.TAG) +@Tag(SmallTests.TAG) public class TestHRegionInfo { - @ClassRule - public static final HBaseClassTestRule CLASS_RULE = - HBaseClassTestRule.forClass(TestHRegionInfo.class); @Test public void testCreateHRegionInfoName() throws Exception { @@ -107,22 +104,35 @@ public class TestHRegionInfo { } ``` -Here the Test Class is `TestHRegionInfo`. The `CLASS_RULE` has -the same form in every test class only the `.class` you pass -is that of the local test; i.e. in the TestTimeout Test Class, you'd -pass `TestTimeout.class` to the `CLASS_RULE` instead of the -`TestHRegionInfo.class` we have above. The `CLASS_RULE` -is where we'll enforce timeouts (currently set at a hard-limit of -thirteen! minutes for all tests — 780 seconds) and other cross-unit test facility. -The test is in the `SmallTest` Category. - -Categories can be arbitrary and provided as a list but each test MUST -carry one from the following list of sizings: `small`, `medium`, `large`, and -`integration`. The test sizing is designated using the JUnit -[categories](https://github.com/junit-team/junit4/wiki/Categories): `SmallTests`, `MediumTests`, `LargeTests`, `IntegrationTests`. -JUnit Categories are denoted using java annotations (a special unit test looks -for the presence of the @Category annotation in all unit tess and will fail if it -finds a test suite missing a sizing marking). +Here the Test Class is `TestHRegionInfo`, tagged as a `SmallTests` (plus the functional +tag `RegionServerTests`). Prior to the JUnit 5 migration, tests were categorized with +`@Category(...)` and, as of `hbase-2.0.0`, had to declare an `HBaseClassTestRule` +`@ClassRule` — both are gone now. The cross-cutting facilities the old `ClassRule` +provided are now applied automatically by the `HBaseJupiterExtension`: + +- it enforces a per-class timeout derived from the sizing tag (three minutes for `small`, + six for `medium`, and a hard limit of thirteen! minutes — 780 seconds — for `large`; + `integration` tests have no timeout); +- it dumps a thread stack to the log output when a test class times out; +- it runs the resource-leak checker (see the [Test Resource Checker](/docs/building-and-developing/tests#test-resource-checker) section below); +- and it fails any test class that is missing a sizing tag. + +`HBaseJupiterExtension` is auto-registered for the Surefire unit-test runs in every module +through the JUnit 5 `ServiceLoader` mechanism +(`junit.jupiter.extensions.autodetection.enabled=true` in the Surefire configuration plus a +`META-INF/services/org.junit.jupiter.api.extension.Extension` entry), so individual test +classes no longer need any `@ClassRule` boilerplate. Integration tests (run under Failsafe +or the `IntegrationTestsDriver`) do not currently enable this auto-registration. + +Tags can be arbitrary and provided as a list, but each test MUST carry one from the +following list of sizings: `small`, `medium`, `large`, and `integration`. The test sizing +is designated with a JUnit 5 +[tag](https://docs.junit.org/5.13.4/user-guide/#writing-tests-tagging-and-filtering) +referencing one of `SmallTests`, `MediumTests`, `LargeTests`, or `IntegrationTests` — use +the `TAG` constant on each of those interfaces in the +`org.apache.hadoop.hbase.testclassification` package (a special extension checks for the +presence of a sizing `@Tag` in all unit tests and will fail if it finds a test class +missing a sizing marking). The first three categories, `small`, `medium`, and `large`, are for test cases which run when you type `$ mvn test`. @@ -291,11 +301,13 @@ Running `./dev-support/hbasetests.sh replayFailed` will rerun the failed tests a ### Test Timeouts -The HBase unit test sizing Categorization timeouts are not strictly enforced. - -Any test that runs longer than ten minutes will be timedout/killed. +`HBaseJupiterExtension` enforces a per-class timeout based on the sizing tag: three minutes +for `small`, six minutes for `medium`, and thirteen minutes for `large` tests +(`integration` tests have no timeout). When a test class exceeds its deadline it is failed +and a thread dump is logged. Independently, Surefire kills any forked JVM that runs longer +than `surefire.timeout` (900 seconds by default). -As of hbase-2.0.0, we have purged all per-test-method timeouts: i.e. +As of hbase-2.0.0, we have purged the old JUnit 4 per-test-method timeouts, i.e. ```java ... @@ -305,15 +317,16 @@ As of hbase-2.0.0, we have purged all per-test-method timeouts: i.e. } ``` -They are discouraged and don't make much sense given we are timing -base of how long the whole Test Fixture/Class/Suite takes and +Blanket per-method timeouts are discouraged and don't make much sense given we are timing +based on how long the whole Test Fixture/Class/Suite takes and that the variance in how long a test method takes varies wildly dependent upon context (loaded Apache Infrastructure versus -developer machine with nothing else running on it). +developer machine with nothing else running on it). JUnit 5's `@Timeout` may still be +applied sparingly to an individual method that genuinely needs its own deadline. ### Test Resource Checker -A custom Maven SureFire plugin listener checks a number of resources before and after each HBase unit test runs and logs its findings at the end of the test output files which can be found in _target/surefire-reports_ per Maven module (Tests write test reports named for the test class into this directory. +The `ResourceChecker`, wired in through `HBaseJupiterExtension`'s `BeforeEachCallback`/`AfterEachCallback`, checks a number of resources before and after each HBase unit test runs and logs its findings in the test output files which can be found in _target/surefire-reports_ per Maven module (Tests write test reports named for the test class into this directory. Check the _\*-out.txt_ files). The resources counted are the number of threads, the number of file descriptors, etc. If the number has increased, it adds a _LEAK?_ comment in the logs. As you can have an HBase instance running in the background, some threads can be deleted/created without any specific action in the test. @@ -369,42 +382,39 @@ This will allow to share the cluster later. ### Tests Skeleton Code -Here is a test skeleton code with Categorization and a Category-based timeout rule to copy and paste and use as basis for test contribution. +Here is a test skeleton code with sizing tags to copy and paste and use as basis for test contribution. ```java /** - * Describe what this testcase tests. Talk about resources initialized in @BeforeClass (before + * Describe what this testcase tests. Talk about resources initialized in @BeforeAll (before * any test is run) and before each test is run, etc. */ -// Specify the category as explained in Unit Tests section. -@Category(SmallTests.class) +// Specify the sizing tag as explained in the Unit Tests section. Most tests also carry a +// functional tag such as RegionServerTests, ClientTests, MasterTests, etc. +@Tag(RegionServerTests.TAG) +@Tag(SmallTests.TAG) public class TestExample { // Replace the TestExample.class in the below with the name of your test fixture class. - private static final Log LOG = LogFactory.getLog(TestExample.class); - - // Handy test rule that allows you subsequently get the name of the current method. See - // down in 'testExampleFoo()' where we use it to log current test's name. - @Rule public TestName testName = new TestName(); + private static final Logger LOG = LoggerFactory.getLogger(TestExample.class); - // The below rule does two things. It decides the timeout based on the category - // (small/medium/large) of the testcase. This @Rule requires that the full testcase runs - // within this timeout irrespective of individual test methods' times. The second - // feature is we'll dump in the log when the test is done a count of threads still - // running. - @Rule public static TestRule timeout = CategoryBasedTimeout.builder(). - withTimeout(this.getClass()).withLookingForStuckThread(true).build(); + // There is no per-class timeout rule to declare. HBaseJupiterExtension is auto-registered + // and, based on the sizing tag above, enforces the whole-class timeout, dumps a thread + // stack if the class times out, and runs the resource checker. + // + // To get the current test's display name, inject a JUnit 5 TestInfo into your setUp or test + // methods. See down in 'testExampleFoo()' where we log it. - @Before + @BeforeEach public void setUp() throws Exception { } - @After + @AfterEach public void tearDown() throws Exception { } @Test - public void testExampleFoo() { - LOG.info("Running test " + testName.getMethodName()); + public void testExampleFoo(TestInfo testInfo) { + LOG.info("Running test " + testInfo.getDisplayName()); } } ``` @@ -417,7 +427,7 @@ Integration tests are what you would run when you need to more elaborate proofin They are not generally run on the Apache Continuous Integration build server, however, some sites opt to run integration tests as a part of their continuous testing on an actual cluster. Integration tests currently live under the _src/test_ directory in the hbase-it submodule and will match the regex: _*IntegrationTest*.java_. -All integration tests are also annotated with `@Category(IntegrationTests.class)`. +All integration tests are also annotated with `@Tag(IntegrationTests.TAG)`. Integration tests can be run in two modes: using a mini cluster, or against an actual distributed cluster. Maven failsafe is used to run the tests using the mini cluster. @@ -525,10 +535,10 @@ bin/hbase [--config config_dir] org.apache.hadoop.hbase.IntegrationTestsDriver ``` Pass `-h` to get usage on this sweet tool. -Running the IntegrationTestsDriver without any argument will launch tests found under `hbase-it/src/test`, having `@Category(IntegrationTests.class)` annotation, and a name starting with `IntegrationTests`. +Running the IntegrationTestsDriver without any argument will launch tests found under `hbase-it/src/test`, having `@Tag(IntegrationTests.TAG)` annotation, and a name starting with `IntegrationTest`. See the usage, by passing -h, to see how to filter test classes. You can pass a regex which is checked against the full class name; so, part of class name can be used. -IntegrationTestsDriver uses Junit to run the tests. +IntegrationTestsDriver uses JUnit 5 to run the tests. Currently there is no support for running integration tests against a distributed cluster using maven (see [HBASE-6201](https://issues.apache.org/jira/browse/HBASE-6201)). The tests interact with the distributed cluster by using the methods in the `DistributedHBaseCluster` (implementing `HBaseCluster`) class, which in turn uses a pluggable `ClusterManager`. diff --git a/hbase-website/app/pages/_docs/docs/_mdx/(multi-page)/unit-testing.mdx b/hbase-website/app/pages/_docs/docs/_mdx/(multi-page)/unit-testing.mdx index 76725c4d25a9..61ca027d13f2 100644 --- a/hbase-website/app/pages/_docs/docs/_mdx/(multi-page)/unit-testing.mdx +++ b/hbase-website/app/pages/_docs/docs/_mdx/(multi-page)/unit-testing.mdx @@ -39,13 +39,16 @@ The first step is to add JUnit dependencies to your Maven POM file: ```xml - junit - junit - 4.11 + org.junit.jupiter + junit-jupiter + 5.13.4 test ``` +Make sure your build uses a Surefire 3.x version (for example 3.5.3); older 2.x Surefire +does not run JUnit 5.13.x tests and reports `Tests run: 0`. + Next, add some unit tests to your code. Tests are annotated with `@Test`. Here, the unit tests are in bold. @@ -68,7 +71,7 @@ public class TestMyHbaseDAOData { These tests ensure that your `createPut` method creates, populates, and returns a `Put` object with expected values. Of course, JUnit can do much more than this. -For an introduction to JUnit, see https://github.com/junit-team/junit/wiki/Getting-started. +For an introduction to JUnit, see https://docs.junit.org/5.13.4/user-guide/. ## Mockito @@ -81,21 +84,28 @@ For instance, you can mock a `org.apache.hadoop.hbase.Server` instance or a `org This example builds upon the example code in [unit.tests](/docs/unit-testing), to test the `insertRecord` method. -First, add a dependency for Mockito to your Maven POM file. +First, add the Mockito dependencies to your Maven POM file. `@ExtendWith(MockitoExtension.class)` +lives in `mockito-junit-jupiter`, so add that alongside `mockito-core`. ```xml org.mockito mockito-core - 2.1.0 + 4.11.0 + test + + + org.mockito + mockito-junit-jupiter + 4.11.0 test ``` -Next, add a `@RunWith` annotation to your test class, to direct it to use Mockito. +Next, add an `@ExtendWith` annotation to your test class, to direct it to use Mockito. ```java -@RunWith(MockitoJUnitRunner.class) +@ExtendWith(MockitoExtension.class) public class TestMyHBaseDAO{ @Mock Configuration config = HBaseConfiguration.create(); @@ -182,7 +192,7 @@ public class MyReducerTest { byte[] CF = "CF".getBytes(); byte[] QUALIFIER = "CQ-1".getBytes(); - @Before + @BeforeEach public void setUp() { MyReducer reducer = new MyReducer(); reduceDriver = ReduceDriver.newReduceDriver(reducer); @@ -250,7 +260,7 @@ public class MyHBaseIntegrationTest { byte[] CQ1 = "CQ-1".getBytes(); byte[] CQ2 = "CQ-2".getBytes(); - @Before + @BeforeEach public void setup() throws Exception { utility = new HBaseTestingUtility(); utility.startMiniCluster(); @@ -295,7 +305,7 @@ public class MyHBaseIntegrationTest { byte[] CQ1 = "CQ-1".getBytes(); byte[] CQ2 = "CQ-2".getBytes(); - @Before + @BeforeEach public void setUp() throws Exception { cluster = TestingHBaseCluster.create(TestingHBaseClusterOption.builder().build()); cluster.start(); @@ -305,7 +315,7 @@ public class MyHBaseIntegrationTest { .setColumnFamily(ColumnFamilyDescriptorBuilder.of(CF)).build()); } - @After + @AfterEach public void tearDown() throws Exception { admin.close(); conn.close(); diff --git a/pom.xml b/pom.xml index 1470fb905bbc..61f041f8dd73 100644 --- a/pom.xml +++ b/pom.xml @@ -1966,10 +1966,6 @@ ${test.exclude.pattern} - - listener - org.apache.hadoop.hbase.TimedOutTestsListener,org.apache.hadoop.hbase.HBaseClassTestRuleChecker,org.apache.hadoop.hbase.ResourceCheckerJUnitListener - junit.jupiter.extensions.autodetection.enabled=true