From a3fd75c309ee9022ec7159f0d9a38f8b8b9a2de6 Mon Sep 17 00:00:00 2001 From: CodeCaster Date: Sun, 4 Jan 2026 09:33:40 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E6=B5=8B=E8=AF=95?= =?UTF-8?q?=E7=AB=AF=E5=8F=A3=E5=88=86=E9=85=8D=E7=9A=84=20TOCTOU=20?= =?UTF-8?q?=E7=AB=9E=E6=80=81=E6=9D=A1=E4=BB=B6=20(#396)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit **问题描述**: TestUtils.getLocalAvailablePort() 存在 TOCTOU (Time of Check, Time of Use) 竞态条件: ServerSocket 获取可用端口后立即关闭,在 Netty 绑定前可能被其他进程抢占。 **核心改进**: - 使用 Netty bind(0) 自动分配端口,彻底消除 TOCTOU 竞态 - 引入 httpBound/httpsBound 标志明确跟踪绑定状态 - 添加 getActualHttpPort() 和 getActualHttpsPort() 方法 - MockMvcListener 等待服务器启动后动态获取实际端口 - 完善的错误处理和详细的错误消息(区分自动分配和指定端口场景) - 移除 TestUtils.getLocalAvailablePort() 方法 **技术实现**: 1. NettyHttpClassicServer: 支持端口 0,bind().sync() 后从 Channel 获取实际端口 2. HttpClassicServer: 新增接口方法获取实际绑定端口 3. MockMvcListener: 移除端口预分配,改为启动后动态获取 4. DefaultFitTestService: 使用端口 0 和无参构造函数 **测试改进**: - 使用 Mockito mock HttpClassicServer,提升测试隔离性 - 添加 StubBeans 实现返回 mock server - 更新测试断言以匹配新的错误消息 **测试状态**:✅ 所有测试通过 相关 Issue: #396 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 --- .../server/netty/NettyHttpClassicServer.java | 71 ++++++++---- .../fit/server/http/FitHttpServer.java | 6 +- .../fit/http/server/HttpClassicServer.java | 18 ++++ .../test/domain/listener/MockMvcListener.java | 80 +++++++++----- .../test/domain/util/TestUtils.java | 33 ------ .../test/service/DefaultFitTestService.java | 7 +- .../domain/listener/MockMvcListenerTest.java | 101 ++++++++++++++++-- 7 files changed, 219 insertions(+), 97 deletions(-) delete mode 100644 framework/fit/java/fit-test/fit-test-framework/src/main/java/modelengine/fitframework/test/domain/util/TestUtils.java diff --git a/framework/fit/java/fit-builtin/plugins/fit-http-server-netty/src/main/java/modelengine/fit/http/server/netty/NettyHttpClassicServer.java b/framework/fit/java/fit-builtin/plugins/fit-http-server-netty/src/main/java/modelengine/fit/http/server/netty/NettyHttpClassicServer.java index 1cf816c6..c7d41c74 100644 --- a/framework/fit/java/fit-builtin/plugins/fit-http-server-netty/src/main/java/modelengine/fit/http/server/netty/NettyHttpClassicServer.java +++ b/framework/fit/java/fit-builtin/plugins/fit-http-server-netty/src/main/java/modelengine/fit/http/server/netty/NettyHttpClassicServer.java @@ -6,7 +6,7 @@ package modelengine.fit.http.server.netty; -import static modelengine.fitframework.inspection.Validation.greaterThan; +import static modelengine.fitframework.inspection.Validation.greaterThanOrEquals; import static modelengine.fitframework.inspection.Validation.isTrue; import static modelengine.fitframework.inspection.Validation.lessThanOrEquals; import static modelengine.fitframework.inspection.Validation.notNull; @@ -52,6 +52,7 @@ import modelengine.fitframework.value.ValueFetcher; import java.io.IOException; +import java.net.InetSocketAddress; import java.security.GeneralSecurityException; import java.util.Arrays; import java.util.Collections; @@ -94,8 +95,10 @@ public class NettyHttpClassicServer implements HttpClassicServer { log.error("Failed to start netty http server.", exception); })); private final ChannelGroup channelGroup = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE); - private volatile int httpPort; - private volatile int httpsPort; + private volatile int httpPort = 0; + private volatile int httpsPort = 0; + private volatile boolean httpBound = false; + private volatile boolean httpsBound = false; private final boolean isGracefulExit; private volatile boolean isStarted = false; private final Lock lock = LockUtils.newReentrantLock(); @@ -131,9 +134,9 @@ public HttpClassicServer bind(int port, boolean isSecure) { return this; } if (isSecure) { - this.httpsPort = greaterThan(port, + this.httpsPort = greaterThanOrEquals(port, 0, - "The port to bind to netty http server cannot be less than 1. [port={0}, isSecure={1}]", + "The port to bind to netty http server cannot be negative. [port={0}, isSecure={1}]", port, true); this.httpsPort = lessThanOrEquals(port, @@ -141,10 +144,11 @@ public HttpClassicServer bind(int port, boolean isSecure) { "The port to bind to netty http server cannot be more than 65535. [port={0}, isSecure={1}]", port, true); + this.httpsBound = true; } else { - this.httpPort = greaterThan(port, + this.httpPort = greaterThanOrEquals(port, 0, - "The port to bind to netty http server cannot be less than 1. [port={0}, isSecure={1}]", + "The port to bind to netty http server cannot be negative. [port={0}, isSecure={1}]", port, false); this.httpPort = lessThanOrEquals(port, @@ -152,6 +156,7 @@ public HttpClassicServer bind(int port, boolean isSecure) { "The port to bind to netty http server cannot be more than 65535. [port={0}, isSecure={1}]", port, false); + this.httpBound = true; } return this; } @@ -161,7 +166,7 @@ public void start() { if (this.isStarted) { return; } - isTrue(this.httpPort > 0 || this.httpsPort > 0, + isTrue(this.httpBound || this.httpsBound, "At least 1 port should be bound to netty http server. [httpPort={0}, httpsPort={1}]", this.httpPort, this.httpsPort); @@ -177,6 +182,22 @@ public boolean isStarted() { return this.isStarted; } + @Override + public int getActualHttpPort() { + if (!this.isStarted || !this.httpBound) { + return 0; + } + return Math.max(this.httpPort, 0); + } + + @Override + public int getActualHttpsPort() { + if (!this.isStarted || !this.httpsBound) { + return 0; + } + return Math.max(this.httpsPort, 0); + } + @Override public void stop() { if (!this.isStarted) { @@ -202,27 +223,32 @@ private void startServer() { EventLoopGroup workerGroup = this.createWorkerGroup(); try { SSLContext sslContext = null; - if (this.httpsPort > 0 && this.httpsConfig.isSslEnabled()) { + if (this.httpsBound && this.httpsConfig != null && this.httpsConfig.isSslEnabled()) { sslContext = this.createSslContext(); } - ChannelHandler channelHandler = new ChannelInitializerHandler(this, - this.getAssemblerConfig(), - this.httpsPort, - sslContext, - this.httpsConfig); + ChannelHandler channelHandler = + new ChannelInitializerHandler(this, this.getAssemblerConfig(), sslContext, this.httpsConfig); ServerBootstrap serverBootstrap = new ServerBootstrap(); serverBootstrap.group(bossGroup, workerGroup) .channel(NioServerSocketChannel.class) .childHandler(channelHandler); - this.logServerStarted(); - if (this.httpPort > 0) { + if (this.httpBound) { Channel channel = serverBootstrap.bind(this.httpPort).sync().channel(); this.channelGroup.add(channel); + if (this.httpPort == 0) { + this.httpPort = ((InetSocketAddress) channel.localAddress()).getPort(); + log.info("HTTP server bound to auto-assigned port: {}", this.httpPort); + } } - if (this.httpsPort > 0) { + if (this.httpsBound) { Channel channel = serverBootstrap.bind(this.httpsPort).sync().channel(); this.channelGroup.add(channel); + if (this.httpsPort == 0) { + this.httpsPort = ((InetSocketAddress) channel.localAddress()).getPort(); + log.info("HTTPS server bound to auto-assigned port: {}", this.httpsPort); + } } + this.logServerStarted(); ChannelGroupFuture channelFutures = this.channelGroup.newCloseFuture(); this.isStarted = true; channelFutures.sync(); @@ -353,7 +379,7 @@ private static class ChannelInitializerHandler extends ChannelInitializer secure = httpConfig.secure(); this.httpsPort = secure.flatMap(ServerConfig.Secure::port).orElse(DEFAULT_HTTPS_PORT); - greaterThan(this.httpsPort, 0, "The server https port must be positive."); + greaterThanOrEquals(this.httpsPort, 0, "The server https port must be non-negative."); log.debug("Config 'server.https.port' is {}.", this.httpsPort); this.toRegisterHttpsPort = secure.flatMap(ServerConfig.Secure::toRegisterPort).orElse(this.httpsPort); log.debug("Config 'server.https.to-register-port' is {}.", this.toRegisterHttpsPort); @@ -78,7 +78,7 @@ public FitHttpServer(HttpClassicServer httpServer, FitHttpHandlerRegistry regist this.httpOpen = httpConfig.isProtocolEnabled() || !this.httpsOpen; if (this.httpOpen) { this.httpPort = httpConfig.port().orElse(DEFAULT_HTTP_PORT); - greaterThan(this.httpPort, 0, "The server http port must be positive."); + greaterThanOrEquals(this.httpPort, 0, "The server http port must be non-negative."); log.debug("Config 'server.http.port' is {}.", this.httpPort); this.toRegisterHttpPort = httpConfig.toRegisterPort().orElse(this.httpPort); log.debug("Config 'server.http.to-register-port' is {}.", this.toRegisterHttpPort); diff --git a/framework/fit/java/fit-builtin/services/fit-http-classic/definition/src/main/java/modelengine/fit/http/server/HttpClassicServer.java b/framework/fit/java/fit-builtin/services/fit-http-classic/definition/src/main/java/modelengine/fit/http/server/HttpClassicServer.java index 60c21fd6..9cd17eb5 100644 --- a/framework/fit/java/fit-builtin/services/fit-http-classic/definition/src/main/java/modelengine/fit/http/server/HttpClassicServer.java +++ b/framework/fit/java/fit-builtin/services/fit-http-classic/definition/src/main/java/modelengine/fit/http/server/HttpClassicServer.java @@ -49,6 +49,24 @@ public interface HttpClassicServer extends HttpResource { */ boolean isStarted(); + /** + * 获取 HTTP 服务实际绑定的端口。 + *

注意:此方法应在服务器启动后({@link #isStarted()} 返回 {@code true})调用。

+ *

如果在 {@link #bind(int)} 时传入 {@code 0},此方法返回操作系统自动分配的实际端口。

+ * + * @return 表示 HTTP 服务实际绑定的端口号,未绑定或未启动时返回 {@code 0}。 + */ + int getActualHttpPort(); + + /** + * 获取 HTTPS 服务实际绑定的端口。 + *

注意:此方法应在服务器启动后({@link #isStarted()} 返回 {@code true})调用。

+ *

如果在 {@link #bind(int, boolean)} 时传入 {@code 0},此方法返回操作系统自动分配的实际端口。

+ * + * @return 表示 HTTPS 服务实际绑定的端口号,未绑定或未启动时返回 {@code 0}。 + */ + int getActualHttpsPort(); + /** * 停止 Http 服务器。 */ diff --git a/framework/fit/java/fit-test/fit-test-framework/src/main/java/modelengine/fitframework/test/domain/listener/MockMvcListener.java b/framework/fit/java/fit-test/fit-test-framework/src/main/java/modelengine/fitframework/test/domain/listener/MockMvcListener.java index 5b1e8436..3dfe4ad8 100644 --- a/framework/fit/java/fit-test/fit-test-framework/src/main/java/modelengine/fitframework/test/domain/listener/MockMvcListener.java +++ b/framework/fit/java/fit-test/fit-test-framework/src/main/java/modelengine/fitframework/test/domain/listener/MockMvcListener.java @@ -9,6 +9,7 @@ import modelengine.fit.http.client.HttpClassicClientResponse; import modelengine.fit.http.entity.TextEntity; import modelengine.fitframework.exception.ClientException; +import modelengine.fit.http.server.HttpClassicServer; import modelengine.fitframework.test.annotation.EnableMockMvc; import modelengine.fitframework.test.domain.TestContext; import modelengine.fitframework.test.domain.mvc.MockController; @@ -43,16 +44,7 @@ public class MockMvcListener implements TestListener { private static final long MIN_STARTUP_TIMEOUT = 1_000L; private static final long MAX_STARTUP_TIMEOUT = 600_000L; - private final int port; - - /** - * 通过插件端口初始化 {@link MockMvcListener} 的实例。 - * - * @param port 表示插件启动端口的 {code int}。 - */ - public MockMvcListener(int port) { - this.port = port; - } + public MockMvcListener() {} @Override public Optional config(Class clazz) { @@ -73,18 +65,33 @@ public void beforeTestClass(TestContext context) { if (AnnotationUtils.getAnnotation(testClass, EnableMockMvc.class).isEmpty()) { return; } - MockMvc mockMvc = new MockMvc(this.port); - context.plugin().container().registry().register(mockMvc); long timeout = this.getStartupTimeout(); long startTime = System.currentTimeMillis(); - boolean started = this.isStarted(mockMvc); + HttpClassicServer server = this.getHttpServer(context); + while (!server.isStarted()) { + long elapsed = System.currentTimeMillis() - startTime; + if (elapsed > timeout) { + throw new IllegalStateException(this.buildTimeoutErrorMessage(elapsed, 0)); + } + ThreadUtils.sleep(100); + } + int actualPort = server.getActualHttpPort(); + if (actualPort <= 0) { + throw new IllegalStateException(StringUtils.format( + "Failed to resolve actual HTTP port from server. [started={0}, httpPort={1}]", + server.isStarted(), + actualPort)); + } + MockMvc mockMvc = new MockMvc(actualPort); + context.plugin().container().registry().register(mockMvc); + boolean started = this.isStarted(mockMvc, actualPort); while (!started) { long elapsed = System.currentTimeMillis() - startTime; if (elapsed > timeout) { - throw new IllegalStateException(this.buildTimeoutErrorMessage(elapsed, this.port)); + throw new IllegalStateException(this.buildTimeoutErrorMessage(elapsed, actualPort)); } ThreadUtils.sleep(100); - started = this.isStarted(mockMvc); + started = this.isStarted(mockMvc, actualPort); } } @@ -108,37 +115,62 @@ private long getStartupTimeout() { } private String buildTimeoutErrorMessage(long elapsed, int port) { + if (port > 0) { + return StringUtils.format(""" + Mock MVC server failed to start within {0}ms. [port={1}] + + Possible causes: + 1. Port {1} is already in use by another process + 2. Network configuration issues + 3. Server startup is slower than expected in this environment + + Troubleshooting steps: + - Check if port {1} is in use: + * macOS/Linux: lsof -i :{1} + * Windows: netstat -ano | findstr :{1} + - Check server logs for detailed error messages + - If running in a slow environment, increase timeout: + mvn test -D{2}=60000""", + elapsed, + port, + TIMEOUT_PROPERTY_KEY); + } return StringUtils.format(""" - Mock MVC server failed to start within {0}ms. [port={1}] + Mock MVC server failed to start within {0}ms. [auto-assigned port] Possible causes: - 1. Port {1} is already in use by another process + 1. Port conflict with another process 2. Network configuration issues 3. Server startup is slower than expected in this environment Troubleshooting steps: - - Check if port {1} is in use: - * macOS/Linux: lsof -i :{1} - * Windows: netstat -ano | findstr :{1} - Check server logs for detailed error messages - If running in a slow environment, increase timeout: - mvn test -D{2}=60000""", + mvn test -D{1}=60000""", elapsed, - port, TIMEOUT_PROPERTY_KEY); } - protected boolean isStarted(MockMvc mockMvc) { + protected boolean isStarted(MockMvc mockMvc, int port) { MockRequestBuilder builder = MockMvcRequestBuilders.get(MockController.PATH).responseType(String.class); try (HttpClassicClientResponse response = mockMvc.perform(builder)) { String content = response.textEntity() .map(TextEntity::content) .orElseThrow(() -> new IllegalStateException(StringUtils.format( "Failed to start mock http server. [port={0}]", - this.port))); + port))); return Objects.equals(content, MockController.OK); } catch (IOException | ClientException e) { return false; } } + + private HttpClassicServer getHttpServer(TestContext context) { + HttpClassicServer server = context.plugin().container().beans().get(HttpClassicServer.class); + if (server == null) { + throw new IllegalStateException("HttpClassicServer not found in container."); + } + return server; + } + } diff --git a/framework/fit/java/fit-test/fit-test-framework/src/main/java/modelengine/fitframework/test/domain/util/TestUtils.java b/framework/fit/java/fit-test/fit-test-framework/src/main/java/modelengine/fitframework/test/domain/util/TestUtils.java deleted file mode 100644 index 53735102..00000000 --- a/framework/fit/java/fit-test/fit-test-framework/src/main/java/modelengine/fitframework/test/domain/util/TestUtils.java +++ /dev/null @@ -1,33 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) 2024 Huawei Technologies Co., Ltd. All rights reserved. - * This file is a part of the ModelEngine Project. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -package modelengine.fitframework.test.domain.util; - -import modelengine.fitframework.test.domain.mvc.MockMvc; - -import java.io.IOException; -import java.net.ServerSocket; - -/** - * 为模拟 {@link MockMvc} 提供常见的公用方法。 - * - * @author 王攀博 - * @since 2024-04-09 - */ -public class TestUtils { - /** - * 获取当前设备可用的端口。 - * - * @return 返回当前设备可用的端口号 {@link int}。 - */ - public static int getLocalAvailablePort() { - try (ServerSocket serverSocket = new ServerSocket(0)) { - return serverSocket.getLocalPort(); - } catch (IOException e) { - throw new IllegalStateException("Failed to get local available port.", e); - } - } -} diff --git a/framework/fit/java/fit-test/fit-test-framework/src/main/java/modelengine/fitframework/test/service/DefaultFitTestService.java b/framework/fit/java/fit-test/fit-test-framework/src/main/java/modelengine/fitframework/test/service/DefaultFitTestService.java index f76abefd..60696ba5 100644 --- a/framework/fit/java/fit-test/fit-test-framework/src/main/java/modelengine/fitframework/test/service/DefaultFitTestService.java +++ b/framework/fit/java/fit-test/fit-test-framework/src/main/java/modelengine/fitframework/test/service/DefaultFitTestService.java @@ -19,7 +19,6 @@ import modelengine.fitframework.test.domain.listener.SqlExecuteListener; import modelengine.fitframework.test.domain.listener.TestListener; import modelengine.fitframework.test.domain.resolver.TestContextConfiguration; -import modelengine.fitframework.test.domain.util.TestUtils; import modelengine.fitframework.util.ThreadUtils; import java.lang.reflect.Method; @@ -52,12 +51,12 @@ public class DefaultFitTestService implements FitTestService { */ public DefaultFitTestService(Class clazz) { Validation.notNull(clazz, "The test class to create fit test manager cannot be null."); - int port = TestUtils.getLocalAvailablePort(); + int port = 0; List listeners = Arrays.asList(new ResolverListener(), new ScanPackageListener(), new InjectFieldTestListener.ByFit(), new InjectFieldTestListener.BySpy(), - new MockMvcListener(port), + new MockMvcListener(), new DataSourceListener(), new SqlExecuteListener(), new MybatisTestListener()); @@ -101,4 +100,4 @@ public void setTestInfo(Class clazz, Object testInstance, Method method) { this.testContext.testInstance(testInstance); this.testContext.testMethod(method); } -} \ No newline at end of file +} diff --git a/framework/fit/java/fit-test/fit-test-framework/src/test/java/modelengine/fitframework/test/domain/listener/MockMvcListenerTest.java b/framework/fit/java/fit-test/fit-test-framework/src/test/java/modelengine/fitframework/test/domain/listener/MockMvcListenerTest.java index c38f3d49..0d224981 100644 --- a/framework/fit/java/fit-test/fit-test-framework/src/test/java/modelengine/fitframework/test/domain/listener/MockMvcListenerTest.java +++ b/framework/fit/java/fit-test/fit-test-framework/src/test/java/modelengine/fitframework/test/domain/listener/MockMvcListenerTest.java @@ -29,9 +29,11 @@ import modelengine.fitframework.test.domain.TestContext; import modelengine.fitframework.test.domain.mvc.MockMvc; import modelengine.fitframework.util.DisposedCallback; +import modelengine.fit.http.server.HttpClassicServer; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; +import org.mockito.Mockito; import java.lang.reflect.Method; import java.lang.reflect.Type; @@ -59,12 +61,13 @@ void shouldTimeoutWhenServerNotStarted() { int port = 65530; System.setProperty(TIMEOUT_KEY, "1000"); StubBeanRegistry registry = new StubBeanRegistry(); - TestContext context = createContext(registry); + HttpClassicServer server = mockHttpServer(false, 0); + TestContext context = createContext(registry, server); MockMvcListener listener = new MockMvcListenerStub(port, () -> false); assertThatThrownBy(() -> listener.beforeTestClass(context)).isInstanceOf(IllegalStateException.class) .hasMessageContaining("Mock MVC server failed to start within") - .hasMessageContaining("port=" + port) + .hasMessageContaining("auto-assigned port") .hasMessageContaining(TIMEOUT_KEY); } @@ -73,7 +76,8 @@ void shouldStartWhenServerAvailable() { int port = 65531; System.setProperty(TIMEOUT_KEY, "1000"); StubBeanRegistry registry = new StubBeanRegistry(); - TestContext context = createContext(registry); + HttpClassicServer server = mockHttpServer(true, port); + TestContext context = createContext(registry, server); MockMvcListener listener = new MockMvcListenerStub(port, new DelayedStart(150)); listener.beforeTestClass(context); @@ -106,12 +110,24 @@ void shouldClampWhenTooLargeTimeout() throws Exception { } private TestContext createContext(StubBeanRegistry registry) { - StubRootPlugin plugin = new StubRootPlugin(registry); + StubRootPlugin plugin = new StubRootPlugin(registry, mockHttpServer(false, 0)); return new TestContext(MockMvcEnabledTest.class, plugin, Collections.emptyList()); } + private TestContext createContext(StubBeanRegistry registry, HttpClassicServer server) { + StubRootPlugin plugin = new StubRootPlugin(registry, server); + return new TestContext(MockMvcEnabledTest.class, plugin, Collections.emptyList()); + } + + private static HttpClassicServer mockHttpServer(boolean started, int port) { + HttpClassicServer server = Mockito.mock(HttpClassicServer.class); + Mockito.when(server.isStarted()).thenReturn(started); + Mockito.when(server.getActualHttpPort()).thenReturn(port); + return server; + } + private long readStartupTimeout() throws Exception { - MockMvcListener listener = new MockMvcListener(8080); + MockMvcListener listener = new MockMvcListener(); Method method = MockMvcListener.class.getDeclaredMethod("getStartupTimeout"); method.setAccessible(true); return (long) method.invoke(listener); @@ -121,12 +137,12 @@ private static final class MockMvcListenerStub extends MockMvcListener { private final java.util.function.BooleanSupplier startedSupplier; private MockMvcListenerStub(int port, java.util.function.BooleanSupplier startedSupplier) { - super(port); + super(); this.startedSupplier = startedSupplier; } @Override - protected boolean isStarted(MockMvc mockMvc) { + protected boolean isStarted(MockMvc mockMvc, int port) { return this.startedSupplier.getAsBoolean(); } } @@ -191,10 +207,12 @@ public void unsubscribe(BeanRegisteredObserver observer) { private static final class StubBeanContainer implements BeanContainer { private final StubRootPlugin plugin; private final StubBeanRegistry registry; + private final HttpClassicServer server; - private StubBeanContainer(StubRootPlugin plugin, StubBeanRegistry registry) { + private StubBeanContainer(StubRootPlugin plugin, StubBeanRegistry registry, HttpClassicServer server) { this.plugin = plugin; this.registry = registry; + this.server = server; } @Override @@ -264,7 +282,7 @@ public void stop() { @Override public Beans beans() { - throw new UnsupportedOperationException("Not needed for MockMvcListenerTest."); + return new StubBeans(this.server); } @Override @@ -297,8 +315,8 @@ public void unsubscribe(DisposedCallback callback) {} private static final class StubRootPlugin implements RootPlugin { private final StubBeanContainer container; - private StubRootPlugin(StubBeanRegistry registry) { - this.container = new StubBeanContainer(this, registry); + private StubRootPlugin(StubBeanRegistry registry, HttpClassicServer server) { + this.container = new StubBeanContainer(this, registry, server); } @Override @@ -413,6 +431,67 @@ public void subscribe(DisposedCallback callback) {} public void unsubscribe(DisposedCallback callback) {} } + private static final class StubBeans implements BeanContainer.Beans { + private final HttpClassicServer server; + + private StubBeans(HttpClassicServer server) { + this.server = server; + } + + @Override + public T get(Class beanClass, Object... initialArguments) { + if (beanClass.isInstance(this.server)) { + return beanClass.cast(this.server); + } + throw new UnsupportedOperationException("Not needed for MockMvcListenerTest."); + } + + @Override + public T get(Type beanType, Object... initialArguments) { + throw new UnsupportedOperationException("Not needed for MockMvcListenerTest."); + } + + @Override + public T get(String beanName, Object... initialArguments) { + throw new UnsupportedOperationException("Not needed for MockMvcListenerTest."); + } + + @Override + public T lookup(Class beanClass, Object... initialArguments) { + return get(beanClass, initialArguments); + } + + @Override + public T lookup(Type beanType, Object... initialArguments) { + throw new UnsupportedOperationException("Not needed for MockMvcListenerTest."); + } + + @Override + public T lookup(String beanName, Object... initialArguments) { + throw new UnsupportedOperationException("Not needed for MockMvcListenerTest."); + } + + @Override + public java.util.Map list(Class beanClass) { + throw new UnsupportedOperationException("Not needed for MockMvcListenerTest."); + } + + @Override + public java.util.Map list(Type beanType) { + throw new UnsupportedOperationException("Not needed for MockMvcListenerTest."); + } + + @Override + public java.util.Map all(Class beanClass) { + throw new UnsupportedOperationException("Not needed for MockMvcListenerTest."); + } + + @Override + public java.util.Map all(Type beanType) { + throw new UnsupportedOperationException("Not needed for MockMvcListenerTest."); + } + } + private static final class StubPluginCollection implements PluginCollection { @Override public int size() {