This is an automated email from the ASF dual-hosted git repository.
paulk-asert pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/groovy.git
The following commit(s) were added to refs/heads/master by this push:
new 0a04376328 try to make JMX tests more resilient
0a04376328 is described below
commit 0a04376328dcec0a3442f3bb3b28723b52c1daf4
Author: Paul King <[email protected]>
AuthorDate: Wed May 13 23:20:24 2026 +1000
try to make JMX tests more resilient
---
.../builder/CgroupV2NpeMitigationExtension.groovy | 79 ++++++++++++++++++
.../jmx/builder/JmxBeanExportFactoryTest.groovy | 2 +
.../groovy/jmx/builder/JmxBeanFactoryTest.groovy | 2 +
.../groovy/jmx/builder/JmxBeansFactoryTest.groovy | 2 +
.../groovy/jmx/builder/JmxBuilderToolsTest.groovy | 2 +
.../builder/JmxClientConnectorFactoryTest.groovy | 2 +
.../jmx/builder/JmxListenerFactoryTest.groovy | 96 ++++++----------------
.../builder/JmxServerConnectorFactoryTest.groovy | 2 +
.../groovy/jmx/builder/JmxTimerFactoryTest.groovy | 2 +
9 files changed, 116 insertions(+), 73 deletions(-)
diff --git
a/subprojects/groovy-jmx/src/test/groovy/groovy/jmx/builder/CgroupV2NpeMitigationExtension.groovy
b/subprojects/groovy-jmx/src/test/groovy/groovy/jmx/builder/CgroupV2NpeMitigationExtension.groovy
new file mode 100644
index 0000000000..61b9fe8b87
--- /dev/null
+++
b/subprojects/groovy-jmx/src/test/groovy/groovy/jmx/builder/CgroupV2NpeMitigationExtension.groovy
@@ -0,0 +1,79 @@
+/*
+ * 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 groovy.jmx.builder
+
+import org.junit.jupiter.api.Assumptions
+import org.junit.jupiter.api.extension.BeforeAllCallback
+import org.junit.jupiter.api.extension.ExtensionContext
+import org.junit.jupiter.api.extension.TestExecutionExceptionHandler
+
+import java.lang.management.ManagementFactory
+import java.util.logging.Level
+import java.util.logging.Logger
+
+/**
+ * Test-only JUnit 5 extension that absorbs the JDK cgroup-v2 first-use NPE
+ * (a null {@code anyController} in {@code CgroupV2Subsystem.getInstance})
+ * sporadically seen on some containerised CI hosts when JMX initialises the
+ * platform OS MXBean.
+ *
+ * <ul>
+ * <li>{@link #beforeAll} primes the affected JDK code paths once per JVM,
+ * so a one-shot race is absorbed before any test runs.</li>
+ * <li>{@link #handleTestExecutionException} converts a deterministic
+ * occurrence into a skipped assumption (via {@link Assumptions#abort})
+ * instead of a test failure.</li>
+ * </ul>
+ *
+ * Applied via {@code @ExtendWith(CgroupV2NpeMitigationExtension)} on JMX
+ * test classes that touch the platform MBean server.
+ */
+class CgroupV2NpeMitigationExtension implements BeforeAllCallback,
TestExecutionExceptionHandler {
+ private static final Logger LOGGER =
Logger.getLogger(CgroupV2NpeMitigationExtension.name)
+ private static volatile boolean primed = false
+
+ @Override
+ void beforeAll(ExtensionContext context) {
+ if (primed) return
+ primed = true
+ try {
+ ManagementFactory.getOperatingSystemMXBean().toString()
+ } catch (Throwable t) {
+ LOGGER.log(Level.FINE, 'Primed OS MXBean init absorbed exception
(likely JDK cgroup-v2)', t)
+ }
+ try {
+ new JmxBuilder().timer(name: 'prime:type=timer-prime', period:
60000)
+ } catch (Throwable t) {
+ LOGGER.log(Level.FINE, 'Primed JmxBuilder.timer absorbed exception
(likely JDK cgroup-v2)', t)
+ }
+ }
+
+ @Override
+ void handleTestExecutionException(ExtensionContext context, Throwable t)
throws Throwable {
+ for (Throwable c = t; c != null; c = c.cause) {
+ String msg = c.message
+ if (c instanceof NullPointerException && msg != null &&
+ (msg.contains('CgroupInfo.getMountPoint') ||
+ (msg.contains('anyController') &&
msg.contains('null')))) {
+ Assumptions.abort("Skipped: known JDK cgroup-v2 NPE in this CI
environment: ${msg}")
+ }
+ }
+ throw t
+ }
+}
diff --git
a/subprojects/groovy-jmx/src/test/groovy/groovy/jmx/builder/JmxBeanExportFactoryTest.groovy
b/subprojects/groovy-jmx/src/test/groovy/groovy/jmx/builder/JmxBeanExportFactoryTest.groovy
index 797c94ee71..23178cd43c 100644
---
a/subprojects/groovy-jmx/src/test/groovy/groovy/jmx/builder/JmxBeanExportFactoryTest.groovy
+++
b/subprojects/groovy-jmx/src/test/groovy/groovy/jmx/builder/JmxBeanExportFactoryTest.groovy
@@ -22,12 +22,14 @@ import groovy.jmx.GroovyMBean
import org.junit.jupiter.api.Assumptions
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.extension.ExtendWith
import javax.management.MBeanServerConnection
import javax.management.ObjectName
import static groovy.test.GroovyAssert.shouldFail
+@ExtendWith(CgroupV2NpeMitigationExtension)
final class JmxBeanExportFactoryTest {
private JmxBuilder builder
diff --git
a/subprojects/groovy-jmx/src/test/groovy/groovy/jmx/builder/JmxBeanFactoryTest.groovy
b/subprojects/groovy-jmx/src/test/groovy/groovy/jmx/builder/JmxBeanFactoryTest.groovy
index 0cb597b996..93689f891e 100644
---
a/subprojects/groovy-jmx/src/test/groovy/groovy/jmx/builder/JmxBeanFactoryTest.groovy
+++
b/subprojects/groovy-jmx/src/test/groovy/groovy/jmx/builder/JmxBeanFactoryTest.groovy
@@ -21,10 +21,12 @@ package groovy.jmx.builder
import org.junit.jupiter.api.Assumptions
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.extension.ExtendWith
import javax.management.MBeanServerConnection
import javax.management.ObjectName
+@ExtendWith(CgroupV2NpeMitigationExtension)
final class JmxBeanFactoryTest {
private JmxBuilder builder
diff --git
a/subprojects/groovy-jmx/src/test/groovy/groovy/jmx/builder/JmxBeansFactoryTest.groovy
b/subprojects/groovy-jmx/src/test/groovy/groovy/jmx/builder/JmxBeansFactoryTest.groovy
index 52bf63a1c0..7ec36e76cf 100644
---
a/subprojects/groovy-jmx/src/test/groovy/groovy/jmx/builder/JmxBeansFactoryTest.groovy
+++
b/subprojects/groovy-jmx/src/test/groovy/groovy/jmx/builder/JmxBeansFactoryTest.groovy
@@ -21,7 +21,9 @@ package groovy.jmx.builder
import org.junit.jupiter.api.Assumptions
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.extension.ExtendWith
+@ExtendWith(CgroupV2NpeMitigationExtension)
final class JmxBeansFactoryTest {
private JmxBuilder builder
diff --git
a/subprojects/groovy-jmx/src/test/groovy/groovy/jmx/builder/JmxBuilderToolsTest.groovy
b/subprojects/groovy-jmx/src/test/groovy/groovy/jmx/builder/JmxBuilderToolsTest.groovy
index 3e754f7c36..5ac1f64bfe 100644
---
a/subprojects/groovy-jmx/src/test/groovy/groovy/jmx/builder/JmxBuilderToolsTest.groovy
+++
b/subprojects/groovy-jmx/src/test/groovy/groovy/jmx/builder/JmxBuilderToolsTest.groovy
@@ -20,11 +20,13 @@ package groovy.jmx.builder
import org.junit.jupiter.api.Assumptions
import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.extension.ExtendWith
import javax.management.ObjectName
import static groovy.test.GroovyAssert.shouldFail
+@ExtendWith(CgroupV2NpeMitigationExtension)
final class JmxBuilderToolsTest {
@Test
diff --git
a/subprojects/groovy-jmx/src/test/groovy/groovy/jmx/builder/JmxClientConnectorFactoryTest.groovy
b/subprojects/groovy-jmx/src/test/groovy/groovy/jmx/builder/JmxClientConnectorFactoryTest.groovy
index 28cd12b0c5..70e29eb6d4 100644
---
a/subprojects/groovy-jmx/src/test/groovy/groovy/jmx/builder/JmxClientConnectorFactoryTest.groovy
+++
b/subprojects/groovy-jmx/src/test/groovy/groovy/jmx/builder/JmxClientConnectorFactoryTest.groovy
@@ -23,6 +23,7 @@ import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.Assumptions
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.extension.ExtendWith
import javax.management.remote.rmi.RMIConnector
import javax.management.remote.rmi.RMIConnectorServer
@@ -30,6 +31,7 @@ import javax.management.remote.rmi.RMIConnectorServer
import static groovy.test.GroovyAssert.shouldFail
@ForkedJvm
+@ExtendWith(CgroupV2NpeMitigationExtension)
final class JmxClientConnectorFactoryTest {
private JmxBuilder builder
diff --git
a/subprojects/groovy-jmx/src/test/groovy/groovy/jmx/builder/JmxListenerFactoryTest.groovy
b/subprojects/groovy-jmx/src/test/groovy/groovy/jmx/builder/JmxListenerFactoryTest.groovy
index 208621853b..cf35a79fcc 100644
---
a/subprojects/groovy-jmx/src/test/groovy/groovy/jmx/builder/JmxListenerFactoryTest.groovy
+++
b/subprojects/groovy-jmx/src/test/groovy/groovy/jmx/builder/JmxListenerFactoryTest.groovy
@@ -18,40 +18,16 @@
*/
package groovy.jmx.builder
-import org.junit.jupiter.api.Assumptions
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.extension.ExtendWith
import javax.management.ObjectName
-import java.lang.management.ManagementFactory
-import java.util.logging.Level
-import java.util.logging.Logger
import static groovy.test.GroovyAssert.shouldFail
+@ExtendWith(CgroupV2NpeMitigationExtension)
class JmxListenerFactoryTest {
- private static final Logger LOGGER =
Logger.getLogger(JmxListenerFactoryTest.name)
-
- static {
- // Prime the code paths that sporadically NPE on first use in
containerized
- // CI environments — JDK cgroup-v2 detection (CgroupV2Subsystem /
CgroupInfo).
- // JMX MBean registration walks the platform OS MXBean, which on Linux
- // consults cgroup state. Where the trip is a one-shot first-use race,
- // priming here absorbs it. Where the trip is deterministic (the JDK
bug
- // fires on every call in this env), the per-test guard below converts
it
- // into a skipped assumption instead of a failure.
- try {
- ManagementFactory.getOperatingSystemMXBean().toString()
- } catch (Throwable t) {
- LOGGER.log(Level.FINE, 'Primed OS MXBean init absorbed exception
(likely JDK cgroup-v2)', t)
- }
- try {
- new JmxBuilder().timer(name: 'prime:type=timer-prime', period:
60000)
- } catch (Throwable t) {
- LOGGER.log(Level.FINE, 'Primed JmxBuilder.timer absorbed exception
(likely JDK cgroup-v2)', t)
- }
- }
-
def builder
@BeforeEach
@@ -61,62 +37,36 @@ class JmxListenerFactoryTest {
@Test
void testRequiredAttributeFrom() {
- try {
- builder.timer(name: "test:type=timer")
- def lstr = builder.listener(from: "test:type=timer")
- assert lstr
- assert lstr.type == "eventListener"
- assert lstr.from instanceof ObjectName
- assert lstr.from == new ObjectName("test:type=timer")
+ builder.timer(name: "test:type=timer")
+ def lstr = builder.listener(from: "test:type=timer")
+ assert lstr
+ assert lstr.type == "eventListener"
+ assert lstr.from instanceof ObjectName
+ assert lstr.from == new ObjectName("test:type=timer")
- shouldFail {
- lstr = builder.listener(event: "someEvent")
- lstr = builder.listener(from: "test:type=nonExistingObject")
- }
- } catch (Throwable t) {
- skipIfKnownCgroupNpe(t)
- throw t
+ shouldFail {
+ lstr = builder.listener(event: "someEvent")
+ lstr = builder.listener(from: "test:type=nonExistingObject")
}
}
@Test
void testListenerEvent() {
- try {
- def eventCount = 0
- builder.timer(name: "test:type=timer", period: 200).start()
+ def eventCount = 0
+ builder.timer(name: "test:type=timer", period: 200).start()
+ builder.listener(from: "test:type=timer", call: {event ->
+ eventCount = eventCount + 1
+ })
+ sleep 1300
+ assert eventCount > 1
+
+ shouldFail {
+ eventCount = 0
builder.listener(from: "test:type=timer", call: {event ->
eventCount = eventCount + 1
})
- sleep 1300
- assert eventCount > 1
-
- shouldFail {
- eventCount = 0
- builder.listener(from: "test:type=timer", call: {event ->
- eventCount = eventCount + 1
- })
- sleep 700
- assert eventCount == 0
- }
- } catch (Throwable t) {
- skipIfKnownCgroupNpe(t)
- throw t
- }
- }
-
- /**
- * If the given throwable (or any of its causes) is the known JDK cgroup-v2
- * NPE — {@code CgroupInfo.getMountPoint()} on a null controller — abort
the
- * test with an assumption (reported as skipped, not failed). Any other
- * failure propagates unchanged.
- */
- private static void skipIfKnownCgroupNpe(Throwable t) {
- for (Throwable c = t; c != null; c = c.cause) {
- String msg = c.message
- if (c instanceof NullPointerException && msg != null &&
- (msg.contains('CgroupInfo.getMountPoint') ||
msg.contains('anyController') && msg.contains('null'))) {
- Assumptions.abort("Skipped: known JDK cgroup-v2 NPE in this CI
environment: ${msg}")
- }
+ sleep 700
+ assert eventCount == 0
}
}
}
diff --git
a/subprojects/groovy-jmx/src/test/groovy/groovy/jmx/builder/JmxServerConnectorFactoryTest.groovy
b/subprojects/groovy-jmx/src/test/groovy/groovy/jmx/builder/JmxServerConnectorFactoryTest.groovy
index 139a65f46c..dd7cbe588a 100644
---
a/subprojects/groovy-jmx/src/test/groovy/groovy/jmx/builder/JmxServerConnectorFactoryTest.groovy
+++
b/subprojects/groovy-jmx/src/test/groovy/groovy/jmx/builder/JmxServerConnectorFactoryTest.groovy
@@ -21,12 +21,14 @@ package groovy.jmx.builder
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.extension.ExtendWith
import javax.management.remote.JMXConnector
import javax.management.remote.JMXConnectorFactory
import javax.management.remote.JMXServiceURL
import javax.management.remote.rmi.RMIConnectorServer
+@ExtendWith(CgroupV2NpeMitigationExtension)
class JmxServerConnectorFactoryTest {
def builder
int defaultPort = 10995
diff --git
a/subprojects/groovy-jmx/src/test/groovy/groovy/jmx/builder/JmxTimerFactoryTest.groovy
b/subprojects/groovy-jmx/src/test/groovy/groovy/jmx/builder/JmxTimerFactoryTest.groovy
index eb1c238cdf..782ec2ecde 100644
---
a/subprojects/groovy-jmx/src/test/groovy/groovy/jmx/builder/JmxTimerFactoryTest.groovy
+++
b/subprojects/groovy-jmx/src/test/groovy/groovy/jmx/builder/JmxTimerFactoryTest.groovy
@@ -22,12 +22,14 @@ import groovy.jmx.GroovyMBean
import org.junit.jupiter.api.Assumptions
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.extension.ExtendWith
import javax.management.MBeanServerConnection
import javax.management.ObjectName
import static groovy.test.GroovyAssert.shouldFail
+@ExtendWith(CgroupV2NpeMitigationExtension)
final class JmxTimerFactoryTest {
private JmxBuilder builder