This is an automated email from the ASF dual-hosted git repository.
zhangduo pushed a commit to branch branch-2.6
in repository https://gitbox.apache.org/repos/asf/hbase.git
The following commit(s) were added to refs/heads/branch-2.6 by this push:
new b1269b14732 HBASE-29576 Replicate HBaseClassTestRule functionality for
Junit 5 (#7331)
b1269b14732 is described below
commit b1269b1473280d45c0b338c49f0447ca242e284d
Author: Duo Zhang <[email protected]>
AuthorDate: Mon Sep 22 16:11:48 2025 +0800
HBASE-29576 Replicate HBaseClassTestRule functionality for Junit 5 (#7331)
Signed-off-by: Istvan Toth <[email protected]>
(cherry picked from commit 1cd9f29786127f4a6935f4e034d94ea083b12964)
---
.../apache/hadoop/hbase/HBaseJupiterExtension.java | 212 +++++++++++++++++++++
.../hadoop/hbase/TestJUnit5TagConstants.java | 4 -
.../org.junit.jupiter.api.extension.Extension | 16 ++
.../apache/hadoop/hbase/http/TestLdapAdminACL.java | 3 -
.../hadoop/hbase/http/TestLdapHttpServer.java | 3 -
pom.xml | 1 +
6 files changed, 229 insertions(+), 10 deletions(-)
diff --git
a/hbase-common/src/test/java/org/apache/hadoop/hbase/HBaseJupiterExtension.java
b/hbase-common/src/test/java/org/apache/hadoop/hbase/HBaseJupiterExtension.java
new file mode 100644
index 00000000000..ff2ad14fe76
--- /dev/null
+++
b/hbase-common/src/test/java/org/apache/hadoop/hbase/HBaseJupiterExtension.java
@@ -0,0 +1,212 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.hadoop.hbase;
+
+import static org.junit.jupiter.api.Assertions.fail;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+import java.time.Duration;
+import java.time.Instant;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import org.apache.hadoop.hbase.testclassification.IntegrationTests;
+import org.apache.hadoop.hbase.testclassification.LargeTests;
+import org.apache.hadoop.hbase.testclassification.MediumTests;
+import org.apache.hadoop.hbase.testclassification.SmallTests;
+import org.apache.yetus.audience.InterfaceAudience;
+import org.junit.jupiter.api.extension.AfterAllCallback;
+import org.junit.jupiter.api.extension.BeforeAllCallback;
+import org.junit.jupiter.api.extension.ExtensionContext;
+import org.junit.jupiter.api.extension.ExtensionContext.Store;
+import org.junit.jupiter.api.extension.InvocationInterceptor;
+import org.junit.jupiter.api.extension.ReflectiveInvocationContext;
+import org.junit.platform.commons.JUnitException;
+import org.junit.platform.commons.util.ExceptionUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import org.apache.hbase.thirdparty.com.google.common.collect.ImmutableMap;
+import org.apache.hbase.thirdparty.com.google.common.collect.Iterables;
+import org.apache.hbase.thirdparty.com.google.common.collect.Sets;
+import
org.apache.hbase.thirdparty.com.google.common.util.concurrent.ThreadFactoryBuilder;
+
+/**
+ * Class test rule implementation for JUnit5.
+ * <p>
+ * It ensures that all JUnit5 tests should have at least one of {@link
SmallTests},
+ * {@link MediumTests}, {@link LargeTests}, {@link IntegrationTests} tags, and
set timeout based on
+ * the tag.
+ * <p>
+ * It also controls the timeout for the whole test class running, while the
timeout annotation in
+ * JUnit5 can only enforce the timeout for each test method.
+ * <p>
+ * Finally, it also forbid System.exit call in tests. TODO: need to find a new
way as
+ * SecurityManager has been removed since Java 21.
+ */
[email protected]
+public class HBaseJupiterExtension
+ implements InvocationInterceptor, BeforeAllCallback, AfterAllCallback {
+
+ private static final Logger LOG =
LoggerFactory.getLogger(HBaseJupiterExtension.class);
+
+ private static final SecurityManager securityManager = new
TestSecurityManager();
+
+ private static final ExtensionContext.Namespace NAMESPACE =
+ ExtensionContext.Namespace.create(HBaseJupiterExtension.class);
+
+ private static final Map<String, Duration> TAG_TO_TIMEOUT =
+ ImmutableMap.of(SmallTests.TAG, Duration.ofMinutes(3), MediumTests.TAG,
Duration.ofMinutes(6),
+ LargeTests.TAG, Duration.ofMinutes(13), IntegrationTests.TAG,
Duration.ZERO);
+
+ private static final String EXECUTOR = "executor";
+
+ private static final String DEADLINE = "deadline";
+
+ private Duration pickTimeout(ExtensionContext ctx) {
+ Set<String> timeoutTags = TAG_TO_TIMEOUT.keySet();
+ Set<String> timeoutTag = Sets.intersection(timeoutTags, ctx.getTags());
+ if (timeoutTag.isEmpty()) {
+ fail("Test class " + ctx.getDisplayName() + " does not have any of the
following scale tags "
+ + timeoutTags);
+ }
+ if (timeoutTag.size() > 1) {
+ fail("Test class " + ctx.getDisplayName() + " has multiple scale tags "
+ timeoutTag);
+ }
+ return TAG_TO_TIMEOUT.get(Iterables.getOnlyElement(timeoutTag));
+ }
+
+ @Override
+ public void beforeAll(ExtensionContext ctx) throws Exception {
+ // TODO: remove this usage
+ System.setSecurityManager(securityManager);
+ Duration timeout = pickTimeout(ctx);
+ if (timeout.isZero() || timeout.isNegative()) {
+ LOG.info("No timeout for {}", ctx.getDisplayName());
+ // zero means no timeout
+ return;
+ }
+ Instant deadline = Instant.now().plus(timeout);
+ LOG.info("Timeout for {} is {}, it should be finished before {}",
ctx.getDisplayName(), timeout,
+ deadline);
+ ExecutorService executor =
+ Executors.newSingleThreadExecutor(new
ThreadFactoryBuilder().setDaemon(true)
+ .setNameFormat("HBase-Test-" + ctx.getDisplayName() +
"-Main-Thread").build());
+ Store store = ctx.getStore(NAMESPACE);
+ store.put(EXECUTOR, executor);
+ store.put(DEADLINE, deadline);
+ }
+
+ @Override
+ public void afterAll(ExtensionContext ctx) throws Exception {
+ Store store = ctx.getStore(NAMESPACE);
+ ExecutorService executor = store.remove(EXECUTOR, ExecutorService.class);
+ if (executor != null) {
+ executor.shutdownNow();
+ }
+ store.remove(DEADLINE);
+ // reset secutiry manager
+ System.setSecurityManager(null);
+ }
+
+ private <T> T runWithTimeout(Invocation<T> invocation, ExtensionContext ctx)
throws Throwable {
+ Store store = ctx.getStore(NAMESPACE);
+ ExecutorService executor = store.get(EXECUTOR, ExecutorService.class);
+ if (executor == null) {
+ return invocation.proceed();
+ }
+ Instant deadline = store.get(DEADLINE, Instant.class);
+ Instant now = Instant.now();
+ if (!now.isBefore(deadline)) {
+ fail("Test " + ctx.getDisplayName() + " timed out, deadline is " +
deadline);
+ return null;
+ }
+
+ Duration remaining = Duration.between(now, deadline);
+ LOG.info("remaining timeout for {} is {}", ctx.getDisplayName(),
remaining);
+ Future<T> future = executor.submit(() -> {
+ try {
+ return invocation.proceed();
+ } catch (Throwable t) {
+ // follow the same pattern with junit5
+ throw ExceptionUtils.throwAsUncheckedException(t);
+ }
+ });
+ try {
+ return future.get(remaining.toNanos(), TimeUnit.NANOSECONDS);
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ fail("Test " + ctx.getDisplayName() + " interrupted");
+ return null;
+ } catch (ExecutionException e) {
+ throw ExceptionUtils.throwAsUncheckedException(e.getCause());
+ } catch (TimeoutException e) {
+
+ throw new JUnitException(
+ "Test " + ctx.getDisplayName() + " timed out, deadline is " +
deadline, e);
+ }
+ }
+
+ @Override
+ public void interceptBeforeAllMethod(Invocation<Void> invocation,
+ ReflectiveInvocationContext<Method> invocationContext, ExtensionContext
extensionContext)
+ throws Throwable {
+ runWithTimeout(invocation, extensionContext);
+ }
+
+ @Override
+ public void interceptBeforeEachMethod(Invocation<Void> invocation,
+ ReflectiveInvocationContext<Method> invocationContext, ExtensionContext
extensionContext)
+ throws Throwable {
+ runWithTimeout(invocation, extensionContext);
+ }
+
+ @Override
+ public void interceptTestMethod(Invocation<Void> invocation,
+ ReflectiveInvocationContext<Method> invocationContext, ExtensionContext
extensionContext)
+ throws Throwable {
+ runWithTimeout(invocation, extensionContext);
+ }
+
+ @Override
+ public void interceptAfterEachMethod(Invocation<Void> invocation,
+ ReflectiveInvocationContext<Method> invocationContext, ExtensionContext
extensionContext)
+ throws Throwable {
+ runWithTimeout(invocation, extensionContext);
+ }
+
+ @Override
+ public void interceptAfterAllMethod(Invocation<Void> invocation,
+ ReflectiveInvocationContext<Method> invocationContext, ExtensionContext
extensionContext)
+ throws Throwable {
+ runWithTimeout(invocation, extensionContext);
+ }
+
+ @Override
+ public <T> T interceptTestClassConstructor(Invocation<T> invocation,
+ ReflectiveInvocationContext<Constructor<T>> invocationContext,
+ ExtensionContext extensionContext) throws Throwable {
+ return runWithTimeout(invocation, extensionContext);
+ }
+}
diff --git
a/hbase-common/src/test/java/org/apache/hadoop/hbase/TestJUnit5TagConstants.java
b/hbase-common/src/test/java/org/apache/hadoop/hbase/TestJUnit5TagConstants.java
index 43607e17181..3e30b388ab2 100644
---
a/hbase-common/src/test/java/org/apache/hadoop/hbase/TestJUnit5TagConstants.java
+++
b/hbase-common/src/test/java/org/apache/hadoop/hbase/TestJUnit5TagConstants.java
@@ -20,21 +20,17 @@ package org.apache.hadoop.hbase;
import static org.junit.jupiter.api.Assertions.assertEquals;
import java.lang.reflect.Field;
-import java.util.concurrent.TimeUnit;
import org.apache.hadoop.hbase.testclassification.ClientTests;
import org.apache.hadoop.hbase.testclassification.MiscTests;
import org.apache.hadoop.hbase.testclassification.SmallTests;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.Timeout;
/**
* Verify that the values are all correct.
*/
@Tag(MiscTests.TAG)
@Tag(SmallTests.TAG)
-// TODO: this is the timeout for each method, not the whole class
-@Timeout(value = 1, unit = TimeUnit.MINUTES)
public class TestJUnit5TagConstants {
@Test
diff --git
a/hbase-common/src/test/resources/META-INF/services/org.junit.jupiter.api.extension.Extension
b/hbase-common/src/test/resources/META-INF/services/org.junit.jupiter.api.extension.Extension
new file mode 100644
index 00000000000..0cb8a35a1ee
--- /dev/null
+++
b/hbase-common/src/test/resources/META-INF/services/org.junit.jupiter.api.extension.Extension
@@ -0,0 +1,16 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+org.apache.hadoop.hbase.HBaseJupiterExtension
diff --git
a/hbase-http/src/test/java/org/apache/hadoop/hbase/http/TestLdapAdminACL.java
b/hbase-http/src/test/java/org/apache/hadoop/hbase/http/TestLdapAdminACL.java
index c4fd208fa7c..91a3321bdfc 100644
---
a/hbase-http/src/test/java/org/apache/hadoop/hbase/http/TestLdapAdminACL.java
+++
b/hbase-http/src/test/java/org/apache/hadoop/hbase/http/TestLdapAdminACL.java
@@ -21,7 +21,6 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
import java.io.IOException;
import java.net.HttpURLConnection;
-import java.util.concurrent.TimeUnit;
import org.apache.directory.server.annotations.CreateLdapServer;
import org.apache.directory.server.annotations.CreateTransport;
import org.apache.directory.server.core.annotations.ApplyLdifs;
@@ -36,7 +35,6 @@ import org.apache.hadoop.hbase.testclassification.SmallTests;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.Timeout;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -56,7 +54,6 @@ import org.slf4j.LoggerFactory;
"dn: uid=jdoe," + LdapConstants.LDAP_BASE_DN, "cn: John Doe", "sn: Doe",
"objectClass: inetOrgPerson", "uid: jdoe", "userPassword: secure123" })
-@Timeout(value = 1, unit = TimeUnit.MINUTES)
public class TestLdapAdminACL extends LdapServerTestBase {
private static final Logger LOG =
LoggerFactory.getLogger(TestLdapAdminACL.class);
diff --git
a/hbase-http/src/test/java/org/apache/hadoop/hbase/http/TestLdapHttpServer.java
b/hbase-http/src/test/java/org/apache/hadoop/hbase/http/TestLdapHttpServer.java
index 9faa8dc49fb..c4936513fb3 100644
---
a/hbase-http/src/test/java/org/apache/hadoop/hbase/http/TestLdapHttpServer.java
+++
b/hbase-http/src/test/java/org/apache/hadoop/hbase/http/TestLdapHttpServer.java
@@ -21,7 +21,6 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
import java.io.IOException;
import java.net.HttpURLConnection;
-import java.util.concurrent.TimeUnit;
import org.apache.directory.server.annotations.CreateLdapServer;
import org.apache.directory.server.annotations.CreateTransport;
import org.apache.directory.server.core.annotations.ApplyLdifs;
@@ -32,7 +31,6 @@ import org.apache.hadoop.hbase.testclassification.MiscTests;
import org.apache.hadoop.hbase.testclassification.SmallTests;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.Timeout;
/**
* Test class for LDAP authentication on the HttpServer.
@@ -47,7 +45,6 @@ import org.junit.jupiter.api.Timeout;
+ "dc: example\n" + "objectClass: top\n" + "objectClass:
domain\n\n")) })
@ApplyLdifs({ "dn: uid=bjones," + LdapConstants.LDAP_BASE_DN, "cn: Bob Jones",
"sn: Jones",
"objectClass: inetOrgPerson", "uid: bjones", "userPassword: p@ssw0rd" })
-@Timeout(value = 1, unit = TimeUnit.MINUTES)
public class TestLdapHttpServer extends LdapServerTestBase {
private static final String BJONES_CREDENTIALS = "bjones:p@ssw0rd";
diff --git a/pom.xml b/pom.xml
index fe8249af5e5..5b57dcdcd8e 100644
--- a/pom.xml
+++ b/pom.xml
@@ -1713,6 +1713,7 @@
<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>
<executions>