Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions dev-support/hbasetests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
* <li>Should not be run in parallel of other integration tests</li>
* </ul>
* 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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
7 changes: 0 additions & 7 deletions hbase-it/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -257,13 +257,6 @@
<failIfNoTests>false</failIfNoTests>
<testFailureIgnore>false</testFailureIgnore>
</configuration>
<dependencies>
<dependency>
<groupId>org.apache.maven.surefire</groupId>
<artifactId>surefire-junit4</artifactId>
<version>${surefire.version}</version>
</dependency>
</dependencies>
<executions>
<execution>
<id>integration-test</id>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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`.
Expand Down Expand Up @@ -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
...
Expand All @@ -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.
Expand Down Expand Up @@ -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());
}
}
```
Expand All @@ -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.
Expand Down Expand Up @@ -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`.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,16 @@ The first step is to add JUnit dependencies to your Maven POM file:

```xml
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.13.4</version>
<scope>test</scope>
</dependency>
```

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.
Expand All @@ -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

Expand All @@ -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
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>2.1.0</version>
<version>4.11.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-junit-jupiter</artifactId>
<version>4.11.0</version>
<scope>test</scope>
</dependency>
```

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();
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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();
Expand All @@ -305,7 +315,7 @@ public class MyHBaseIntegrationTest {
.setColumnFamily(ColumnFamilyDescriptorBuilder.of(CF)).build());
}

@After
@AfterEach
public void tearDown() throws Exception {
admin.close();
conn.close();
Expand Down
4 changes: 0 additions & 4 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -1966,10 +1966,6 @@
<exclude>${test.exclude.pattern}</exclude>
</excludes>
<properties>
<property>
<name>listener</name>
<value>org.apache.hadoop.hbase.TimedOutTestsListener,org.apache.hadoop.hbase.HBaseClassTestRuleChecker,org.apache.hadoop.hbase.ResourceCheckerJUnitListener</value>
</property>
<configurationParameters>junit.jupiter.extensions.autodetection.enabled=true</configurationParameters>
</properties>
</configuration>
Expand Down