View Javadoc
1   /*
2    * Copyright 2013 The Netty Project
3    *
4    * The Netty Project licenses this file to you under the Apache License,
5    * version 2.0 (the "License"); you may not use this file except in compliance
6    * with the License. You may obtain a copy of the License at:
7    *
8    *   https://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13   * License for the specific language governing permissions and limitations
14   * under the License.
15   */
16  package io.netty5.testsuite.transport.socket;
17  
18  import io.netty5.bootstrap.Bootstrap;
19  import io.netty5.channel.Channel;
20  import io.netty5.channel.ChannelHandler;
21  import io.netty5.channel.ChannelHandlerContext;
22  import io.netty5.channel.ChannelOption;
23  import io.netty5.channel.ConnectTimeoutException;
24  import io.netty5.util.NetUtil;
25  import io.netty5.util.concurrent.Future;
26  import io.netty5.util.concurrent.GlobalEventExecutor;
27  import io.netty5.util.concurrent.Promise;
28  import io.netty5.util.internal.SocketUtils;
29  import io.netty5.util.internal.logging.InternalLoggerFactory;
30  import org.junit.jupiter.api.Test;
31  import org.junit.jupiter.api.TestInfo;
32  import org.junit.jupiter.api.Timeout;
33  
34  import java.net.ConnectException;
35  import java.net.Socket;
36  import java.util.concurrent.ExecutionException;
37  import java.util.concurrent.TimeUnit;
38  
39  import static io.netty5.testsuite.transport.socket.SocketTestPermutation.BAD_HOST;
40  import static io.netty5.testsuite.transport.socket.SocketTestPermutation.BAD_PORT;
41  import static org.assertj.core.api.Assertions.assertThat;
42  import static org.junit.jupiter.api.Assertions.assertFalse;
43  import static org.junit.jupiter.api.Assertions.assertThrows;
44  import static org.junit.jupiter.api.Assertions.fail;
45  import static org.junit.jupiter.api.Assumptions.assumeTrue;
46  
47  public class SocketConnectionAttemptTest extends AbstractClientSocketTest {
48  
49      // See /etc/services
50      private static final int UNASSIGNED_PORT = 4;
51  
52      @Test
53      @Timeout(value = 30000, unit = TimeUnit.MILLISECONDS)
54      public void testConnectTimeout(TestInfo testInfo) throws Throwable {
55          run(testInfo, this::testConnectTimeout);
56      }
57  
58      public void testConnectTimeout(Bootstrap cb) throws Throwable {
59          cb.handler(new TestHandler()).option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 2000);
60          Future<Channel> future = cb.connect(BAD_HOST, BAD_PORT);
61          assertThat(future.asStage().await(3000, TimeUnit.MILLISECONDS)).isTrue();
62          ExecutionException e = assertThrows(ExecutionException.class, future.asStage()::get);
63          assertThat(e).hasCauseInstanceOf(ConnectTimeoutException.class);
64      }
65  
66      @Test
67      @Timeout(value = 30000, unit = TimeUnit.MILLISECONDS)
68      public void testConnectRefused(TestInfo testInfo) throws Throwable {
69          run(testInfo, this::testConnectRefused);
70      }
71  
72      public void testConnectRefused(Bootstrap cb) throws Throwable {
73          testConnectRefused0(cb, false);
74      }
75  
76      @Test
77      @Timeout(value = 30000, unit = TimeUnit.MILLISECONDS)
78      public void testConnectRefusedHalfClosure(TestInfo testInfo) throws Throwable {
79          run(testInfo, this::testConnectRefusedHalfClosure);
80      }
81  
82      public void testConnectRefusedHalfClosure(Bootstrap cb) throws Throwable {
83          testConnectRefused0(cb, true);
84      }
85  
86      private static void testConnectRefused0(Bootstrap cb, boolean halfClosure) throws Throwable {
87          final Promise<Error> errorPromise = GlobalEventExecutor.INSTANCE.newPromise();
88          ChannelHandler handler = new ChannelHandler() {
89              @Override
90              public void channelActive(ChannelHandlerContext ctx) throws Exception {
91                  errorPromise.setFailure(new AssertionError("should have never been called"));
92              }
93          };
94  
95          cb.handler(handler);
96          cb.option(ChannelOption.ALLOW_HALF_CLOSURE, halfClosure);
97          Throwable cause = cb.connect(NetUtil.LOCALHOST, UNASSIGNED_PORT).asStage().getCause();
98          assertThat(cause).isInstanceOf(ConnectException.class);
99          assertFalse(errorPromise.isFailed());
100     }
101 
102     @Test
103     public void testConnectCancellation(TestInfo testInfo) throws Throwable {
104         // Check if the test can be executed or should be skipped because of no network/internet connection
105         // See https://github.com/netty/netty/issues/1474
106         boolean badHostTimedOut = true;
107         try (Socket socket = new Socket()) {
108             SocketUtils.connect(socket, SocketUtils.socketAddress(BAD_HOST, BAD_PORT), 10);
109         } catch (ConnectException e) {
110             badHostTimedOut = false;
111             // is thrown for no route to host when using Socket connect
112         } catch (Exception e) {
113             // ignore
114         }
115         // ignore
116 
117         assumeTrue(badHostTimedOut, "The connection attempt to " + BAD_HOST + " does not time out.");
118 
119         run(testInfo, this::testConnectCancellation);
120     }
121 
122     public void testConnectCancellation(Bootstrap cb) throws Throwable {
123         cb.handler(new TestHandler()).option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 4000);
124         Future<Channel> future = cb.connect(BAD_HOST, BAD_PORT);
125         if (future.asStage().await(1000, TimeUnit.MILLISECONDS)) {
126             if (future.isSuccess()) {
127                 fail("A connection attempt to " + BAD_HOST + " must not succeed.");
128             } else {
129                 throw future.cause();
130             }
131         }
132 
133         if (future.cancel()) {
134             assertThat(future.isCancelled()).isTrue();
135         } else {
136             // Cancellation not supported by the transport.
137             future.asStage().get().close();
138         }
139     }
140 
141     private static class TestHandler implements ChannelHandler {
142         @Override
143         public void channelExceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
144             InternalLoggerFactory.getInstance(
145                     SocketConnectionAttemptTest.class).warn("Unexpected exception:", cause);
146         }
147     }
148 }