Skip to content
Draft
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
90 changes: 90 additions & 0 deletions blackbox-global-scope-teardown/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>avaje-inject-parent</artifactId>
<groupId>io.avaje</groupId>
<version>12.7-RC2</version>
</parent>
<modelVersion>4.0.0</modelVersion>

<artifactId>blackbox-global-scope-teardown</artifactId>

<description>
Reproduces global test scope corruption with real JVM-global state: ServerStatus is a
singleton that registers itself on the platform MBeanServer in PostConstruct and
unregisters in PreDestroy, the standard JMX pattern. T1 (plain InjectTest) passes.
T2 adds one Mockito mock of an unrelated interface and never references ServerStatus;
wiring its test scope rebuilds the entire application beside the live global scope,
constructs ServerStatus a second time, and fails with InstanceAlreadyExistsException.
Closing that failed scope runs the duplicate's PreDestroy, which unregisters (by
ObjectName) the MBean the live global scope owns, so T3 (identical to T1) then fails
with InstanceNotFoundException. T1 and T3 pass in isolation; T2 fails even alone.
</description>

<dependencies>
<dependency>
<groupId>io.avaje</groupId>
<artifactId>avaje-inject</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>jakarta.annotation</groupId>
<artifactId>jakarta.annotation-api</artifactId>
<version>3.0.0</version>
</dependency>
<dependency>
<groupId>io.avaje</groupId>
<artifactId>avaje-inject-test</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.avaje</groupId>
<artifactId>junit</artifactId>
<version>1.8</version>
<scope>test</scope>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>io.avaje</groupId>
<artifactId>avaje-inject-generator</artifactId>
<version>${project.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<runOrder>alphabetical</runOrder>
</configuration>
</plugin>
<!-- generated by avaje inject -->
<plugin>
<groupId>io.avaje</groupId>
<artifactId>avaje-inject-maven-plugin</artifactId>
<version>${project.version}</version>
<executions>
<execution>
<?m2e execute?>
<phase>process-sources</phase>
<goals>
<goal>provides</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package org.example.globalscope;

import jakarta.inject.Singleton;

@Singleton
public class DefaultGreeter implements Greeter {
@Override
public String greet() {
return "hello";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package org.example.globalscope;

public interface Greeter {
String greet();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package org.example.globalscope;

import java.lang.management.ManagementFactory;

import javax.management.JMException;
import javax.management.ObjectName;

import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import jakarta.inject.Singleton;

/**
* Publishes a management view of this component on the JVM-wide platform MBeanServer,
* the standard JMX pattern (HikariCP pools, Kafka clients, Jetty, and the JDK itself).
*/
@Singleton
public class ServerStatus implements ServerStatusMBean {

public static final String OBJECT_NAME = "org.example.globalscope:type=ServerStatus";

@PostConstruct
void register() {
try {
ManagementFactory.getPlatformMBeanServer().registerMBean(this, new ObjectName(OBJECT_NAME));
} catch (JMException e) {
throw new IllegalStateException("failed to register " + OBJECT_NAME, e);
}
}

@PreDestroy
void unregister() {
try {
ManagementFactory.getPlatformMBeanServer().unregisterMBean(new ObjectName(OBJECT_NAME));
} catch (JMException e) {
throw new IllegalStateException("failed to unregister " + OBJECT_NAME, e);
}
}

@Override
public String getStatus() {
return "OK";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package org.example.globalscope;

/** Standard MBean interface for {@link ServerStatus}. */
public interface ServerStatusMBean {

String getStatus();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package org.example.globalscope;

import io.avaje.inject.test.InjectTest;
import jakarta.inject.Inject;
import org.junit.jupiter.api.Test;

import javax.management.ObjectName;
import java.lang.management.ManagementFactory;

import static org.junit.jupiter.api.Assertions.assertEquals;

@InjectTest
class T1GlobalTest {

@Inject ServerStatus serverStatus;

@Test
void mbeanIsRegistered() throws Exception {
Object status = ManagementFactory.getPlatformMBeanServer()
.getAttribute(new ObjectName(ServerStatus.OBJECT_NAME), "Status");
assertEquals("OK", status);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package org.example.globalscope;

import io.avaje.inject.test.InjectTest;
import org.junit.jupiter.api.Test;
import org.mockito.Mock;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.when;

/**
* Never references ServerStatus. Adding one mock of an unrelated interface rebuilds the
* entire application beside the live global scope, so ServerStatus is constructed a second
* time and its duplicate MBean registration fails the wiring.
*/
@InjectTest
class T2MockTest {

@Mock Greeter greeter;

@Test
void mockGreeter() {
when(greeter.greet()).thenReturn("mocked");
assertEquals("mocked", greeter.greet());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package org.example.globalscope;

import io.avaje.inject.test.InjectTest;
import jakarta.inject.Inject;
import org.junit.jupiter.api.Test;

import javax.management.ObjectName;
import java.lang.management.ManagementFactory;

import static org.junit.jupiter.api.Assertions.assertEquals;

/**
* Same test as T1, back on the shared global scope. Fails after T2: closing the
* failed mock scope ran the duplicate ServerStatus preDestroy, which unregistered
* (by ObjectName) the MBean the still-live global scope instance owns.
*/
@InjectTest
class T3GlobalAgainTest {

@Inject ServerStatus serverStatus;

@Test
void mbeanStillRegistered() throws Exception {
Object status = ManagementFactory.getPlatformMBeanServer()
.getAttribute(new ObjectName(ServerStatus.OBJECT_NAME), "Status");
assertEquals("OK", status);
}
}
1 change: 1 addition & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
<module>blackbox-aspect</module>
<module>blackbox-test-inject</module>
<module>blackbox-multi-scope</module>
<module>blackbox-global-scope-teardown</module>
</modules>
</profile>
</profiles>
Expand Down