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 5eb0cbdd8f minor refactor: reduce CI flakiness for JMX (cont'd)
5eb0cbdd8f is described below
commit 5eb0cbdd8f4fd6190912b77cbaf5e03e544b7ebe
Author: Paul King <[email protected]>
AuthorDate: Tue May 12 08:27:39 2026 +1000
minor refactor: reduce CI flakiness for JMX (cont'd)
---
.../jmx/builder/JmxListenerFactoryTest.groovy | 94 +++++++++++++++-------
1 file changed, 65 insertions(+), 29 deletions(-)
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 5e6e562bd3..208621853b 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,27 +18,37 @@
*/
package groovy.jmx.builder
+import org.junit.jupiter.api.Assumptions
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
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
class JmxListenerFactoryTest {
+ private static final Logger LOGGER =
Logger.getLogger(JmxListenerFactoryTest.name)
+
static {
- // Prime java.util.Timer's first-use init to absorb a JDK cgroup-v2
NPE that
- // sporadically surfaces (CgroupV2Subsystem.getInstance) on
containerized CI
- // when the JMX timer test spins up its housekeeper thread. The trip is
- // environmental and one-shot — after the JVM stashes whatever cgroup
state
- // it managed to resolve, later calls succeed.
+ // 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 java.util.Timer(true).cancel()
+ new JmxBuilder().timer(name: 'prime:type=timer-prime', period:
60000)
} catch (Throwable t) {
- Logger.getLogger(JmxListenerFactoryTest.name).log(Level.FINE,
- 'Primed java.util.Timer init absorbed exception (likely JDK
cgroup-v2)', t)
+ LOGGER.log(Level.FINE, 'Primed JmxBuilder.timer absorbed exception
(likely JDK cgroup-v2)', t)
}
}
@@ -51,36 +61,62 @@ class JmxListenerFactoryTest {
@Test
void testRequiredAttributeFrom() {
- 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")
+ 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")
- shouldFail {
- lstr = builder.listener(event: "someEvent")
- lstr = builder.listener(from: "test:type=nonExistingObject")
+ shouldFail {
+ lstr = builder.listener(event: "someEvent")
+ lstr = builder.listener(from: "test:type=nonExistingObject")
+ }
+ } catch (Throwable t) {
+ skipIfKnownCgroupNpe(t)
+ throw t
}
}
@Test
void testListenerEvent() {
- 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
+ try {
+ def eventCount = 0
+ builder.timer(name: "test:type=timer", period: 200).start()
builder.listener(from: "test:type=timer", call: {event ->
eventCount = eventCount + 1
})
- sleep 700
- assert eventCount == 0
+ 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}")
+ }
}
}
}