package org.jooby.internal.netty; import static org.easymock.EasyMock.expect; import static org.easymock.EasyMock.isA; import java.io.File; import java.util.concurrent.ThreadFactory; import org.jooby.spi.HttpHandler; import org.jooby.spi.Server; import org.jooby.test.MockUnit; import org.jooby.test.MockUnit.Block; import org.junit.Test; import org.junit.runner.RunWith; import org.powermock.core.classloader.annotations.PrepareForTest; import org.powermock.modules.junit4.PowerMockRunner; import com.typesafe.config.Config; import com.typesafe.config.ConfigFactory; import com.typesafe.config.ConfigValueFactory; import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.Channel; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelOption; import io.netty.channel.EventLoopGroup; import io.netty.channel.ServerChannel; import io.netty.channel.epoll.Epoll; import io.netty.channel.epoll.EpollEventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.nio.NioServerSocketChannel; import io.netty.handler.logging.LogLevel; import io.netty.handler.logging.LoggingHandler; import io.netty.handler.ssl.SslContextBuilder; import io.netty.util.concurrent.DefaultEventExecutorGroup; import io.netty.util.concurrent.DefaultThreadFactory; import io.netty.util.concurrent.EventExecutorGroup; import io.netty.util.concurrent.Future; import io.netty.util.concurrent.GenericFutureListener; @RunWith(PowerMockRunner.class) @PrepareForTest({NettyServer.class, NioEventLoopGroup.class, DefaultThreadFactory.class, DefaultEventExecutorGroup.class, ServerBootstrap.class, SslContextBuilder.class, File.class, Epoll.class, EpollEventLoopGroup.class, NettySslContext.class }) public class Issue581 { Config config = ConfigFactory.empty() .withValue("server.http2.enabled", ConfigValueFactory.fromAnyRef(false)) .withValue("netty.threads.Boss", ConfigValueFactory.fromAnyRef(1)) .withValue("netty.threads.Worker", ConfigValueFactory.fromAnyRef(0)) .withValue("netty.threads.Max", ConfigValueFactory.fromAnyRef(2)) .withValue("netty.threads.Name", ConfigValueFactory.fromAnyRef("netty task")) .withValue("netty.options.SO_BACKLOG", ConfigValueFactory.fromAnyRef(1024)) .withValue("netty.worker.options.SO_REUSEADDR", ConfigValueFactory.fromAnyRef(true)) .withValue("netty.http.MaxContentLength", ConfigValueFactory.fromAnyRef("200k")) .withValue("netty.http.MaxInitialLineLength", ConfigValueFactory.fromAnyRef("4k")) .withValue("netty.http.MaxHeaderSize", ConfigValueFactory.fromAnyRef("8k")) .withValue("netty.http.MaxChunkSize", ConfigValueFactory.fromAnyRef("8k")) .withValue("netty.http.IdleTimeout", ConfigValueFactory.fromAnyRef("30s")) .withValue("netty.options.CONNECT_TIMEOUT_MILLIS", ConfigValueFactory.fromAnyRef(1000)) .withValue("application.port", ConfigValueFactory.fromAnyRef(6789)) .withValue("application.host", ConfigValueFactory.fromAnyRef("0.0.0.0")); @SuppressWarnings({"rawtypes", "unchecked" }) private MockUnit.Block parentEventLoop = unit -> { NioEventLoopGroup eventLoop = unit.constructor(NioEventLoopGroup.class) .args(int.class, ThreadFactory.class) .build(1, unit.get(ThreadFactory.class)); unit.registerMock(EventLoopGroup.class, eventLoop); unit.registerMock(NioEventLoopGroup.class, eventLoop); Future future = unit.mock(Future.class); unit.registerMock(Future.class, future); expect(future.isSuccess()).andReturn(true).times(2); expect(future.addListener(unit.capture(GenericFutureListener.class))).andReturn(future) .times(2); expect(eventLoop.shutdownGracefully()).andReturn(future).times(2); expect(eventLoop.isShuttingDown()).andReturn(false).times(2); }; private Block taskThreadFactory = unit -> { DefaultThreadFactory factory = unit.constructor(DefaultThreadFactory.class) .args(String.class) .build("netty task"); unit.registerMock(DefaultThreadFactory.class, factory); }; private Block taskExecutor = unit -> { DefaultEventExecutorGroup executor = unit.constructor(DefaultEventExecutorGroup.class) .args(int.class, ThreadFactory.class) .build(2, unit.get(DefaultThreadFactory.class)); unit.registerMock(EventExecutorGroup.class, executor); }; private Block channel = unit -> { ChannelFuture cfuture = unit.mock(ChannelFuture.class); expect(cfuture.sync()).andReturn(cfuture); Channel channel = unit.mock(Channel.class); expect(channel.closeFuture()).andReturn(cfuture); unit.registerMock(Channel.class, channel); }; private Block noepoll = unit -> { unit.mockStatic(Epoll.class); expect(Epoll.isAvailable()).andReturn(false).times(1, 2); }; @SuppressWarnings({"rawtypes", "unchecked" }) @Test public void shouldShutdownExecutor() throws Exception { new MockUnit(HttpHandler.class) .expect(parentThreadFactory("nio-boss")) .expect(noepoll) .expect(parentEventLoop) .expect(taskThreadFactory) .expect(taskExecutor) .expect(channel) .expect(bootstrap(6789)) .expect(unit -> { EventExecutorGroup executor = unit.get(EventExecutorGroup.class); Future future = unit.mock(Future.class); expect(executor.shutdownGracefully()).andReturn(future); expect(executor.isShuttingDown()).andReturn(false); expect(future.addListener(unit.capture(GenericFutureListener.class))).andReturn(future); }) .run(unit -> { NettyServer server = new NettyServer(unit.get(HttpHandler.class), config); try { server.start(); server.join(); } finally { server.stop(); } }, unit -> { unit.captured(GenericFutureListener.class).get(0) .operationComplete(unit.get(Future.class)); unit.captured(GenericFutureListener.class).get(0) .operationComplete(unit.get(Future.class)); }); } private Block parentThreadFactory(final String name) { return unit -> { DefaultThreadFactory factory = unit.constructor(DefaultThreadFactory.class) .args(String.class, int.class) .build(name, false); unit.registerMock(ThreadFactory.class, factory); }; } private Block bootstrap(final int port) { return bootstrap(port, NioEventLoopGroup.class, NioServerSocketChannel.class); } private Block bootstrap(final int port, final Class<? extends EventLoopGroup> eventLoop, final Class<? extends ServerChannel> channelClass) { return unit -> { ServerBootstrap bootstrap = unit.mockConstructor(ServerBootstrap.class); expect(bootstrap.group( unit.get(EventLoopGroup.class), unit.get(eventLoop))) .andReturn(bootstrap); LoggingHandler handler = unit.constructor(LoggingHandler.class) .args(Class.class, LogLevel.class) .build(Server.class, LogLevel.DEBUG); expect(bootstrap.channel(channelClass)).andReturn(bootstrap); expect(bootstrap.handler(handler)).andReturn(bootstrap); expect(bootstrap.childHandler(isA(NettyPipeline.class))).andReturn(bootstrap); expect(bootstrap.option(ChannelOption.SO_BACKLOG, 1024)).andReturn(bootstrap); expect(bootstrap.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 1000)).andReturn(bootstrap); expect(bootstrap.childOption(ChannelOption.SO_REUSEADDR, true)).andReturn(bootstrap); ChannelFuture future = unit.mock(ChannelFuture.class); expect(future.sync()).andReturn(future); expect(future.channel()).andReturn(unit.get(Channel.class)); expect(bootstrap.bind("0.0.0.0", port)).andReturn(future); unit.registerMock(ServerBootstrap.class, bootstrap); }; } }