Repository: netty/netty
File: testsuite/src/main/java/io/netty/testsuite/transport/socket/SocketHalfClosedTest.java (Java)

Code before:
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;

import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assumptions.assumeFalse;

@Timeout(value = 20000, unit = MILLISECONDS)
public class SocketHalfClosedTest extends AbstractSocketTest {

    protected int maxReadCompleteWithNoDataAfterInputShutdown() {
        return 2; // nio needs read flag to detect full closure.
    }

    @Test
    public void testAllDataReadEventTriggeredAfterHalfClosure(TestInfo testInfo) throws Throwable {
        run(testInfo, new Runner<ServerBootstrap, Bootstrap>() {
            @Override
            public void run(ServerBootstrap serverBootstrap, Bootstrap bootstrap) throws Throwable {
                // TODO: there needs to be a more robust way to detect this
                if (bootstrap.toString().contains("NioSocketChannel") ||
                        bootstrap.toString().contains("OioSocketChannel")) {
                    return;
                }
                allDataReadEventTriggeredAfterHalfClosure(serverBootstrap, bootstrap);
            }
        });
    }

    private void allDataReadEventTriggeredAfterHalfClosure(ServerBootstrap sb, Bootstrap cb) throws Throwable {
        final int totalServerBytesWritten = 1;
        final CountDownLatch clientReadAllDataLatch = new CountDownLatch(1);
        final CountDownLatch clientHalfClosedLatch = new CountDownLatch(1);
        final CountDownLatch clientHalfClosedAllBytesRead = new CountDownLatch(1);
        final AtomicInteger clientReadCompletes = new AtomicInteger();
        final AtomicInteger clientZeroDataReadCompletes = new AtomicInteger();
        Channel serverChannel = null;
        Channel clientChannel = null;
        AtomicReference<Channel> serverChildChannel = new AtomicReference<>();
        try {
            cb.option(ChannelOption.ALLOW_HALF_CLOSURE, true)
                    .option(ChannelOption.AUTO_CLOSE, false)
                    .option(ChannelOption.AUTO_READ, false);

            sb.option(ChannelOption.ALLOW_HALF_CLOSURE, true)
                    .option(ChannelOption.AUTO_CLOSE, false)
                    .childOption(ChannelOption.TCP_NODELAY, true);

            sb.childHandler(new ChannelInitializer<Channel>() {

Review comment (refactor):
This is admittedly very janky. There are a few options.
1. Enhance `BootstrapComboFactory`: this will require changing a number of tests.
2. Change the `Bootstrap` to be more inspectable. This expands surface API.
3. Move the test into its own abstract suite and extend in epoll, kqueue, and io_uring. This is easier to forget and this does feel like it belongs in this suite.
4. Keep it as it is. I wouldn't be proud of it but that's okay.

Code after:

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;

import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assumptions.assumeFalse;

@Timeout(value = 20000, unit = MILLISECONDS)
public class SocketHalfClosedTest extends AbstractSocketTest {

    protected int maxReadCompleteWithNoDataAfterInputShutdown() {
        return 2; // nio needs read flag to detect full closure.
    }

    @Test
    public void testAllDataReadEventTriggeredAfterHalfClosure(TestInfo testInfo) throws Throwable {
        run(testInfo, new Runner<ServerBootstrap, Bootstrap>() {
            @Override
            public void run(ServerBootstrap serverBootstrap, Bootstrap bootstrap) throws Throwable {
                if (bootstrap.config().group() instanceof OioEventLoopGroup) {
                    logger.debug("Ignoring test for incompatible OIO event system");
                    return;
                } else if (bootstrap.config().group() instanceof IoEventLoopGroup) {
                    IoEventLoopGroup group = (IoEventLoopGroup) bootstrap.config().group();
                    if (group.isIoType(NioIoHandler.class)) {
                        logger.debug("Ignoring test for incompatible NioHandler");
                        return;
                    }
                }
                allDataReadEventTriggeredAfterHalfClosure(serverBootstrap, bootstrap);
            }
        });
    }

    private void allDataReadEventTriggeredAfterHalfClosure(ServerBootstrap sb, Bootstrap cb) throws Throwable {
        final int totalServerBytesWritten = 1;
        final CountDownLatch clientReadAllDataLatch = new CountDownLatch(1);
        final CountDownLatch clientHalfClosedLatch = new CountDownLatch(1);
        final CountDownLatch clientHalfClosedAllBytesRead = new CountDownLatch(1);
        final AtomicInteger clientReadCompletes = new AtomicInteger();
        final AtomicInteger clientZeroDataReadCompletes = new AtomicInteger();
        Channel serverChannel = null;
        Channel clientChannel = null;
        AtomicReference<Channel> serverChildChannel = new AtomicReference<>();
        try {
            cb.option(ChannelOption.ALLOW_HALF_CLOSURE, true)