1 /*
   2  * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  * or visit www.oracle.com if you need additional information or have any
  21  * questions.
  22  */
  23 package jdk.incubator.http.internal.websocket;
  24 
  25 import org.testng.SkipException;
  26 import org.testng.annotations.Test;
  27 import jdk.incubator.http.internal.websocket.Frame.Opcode;
  28 
  29 import java.io.IOException;
  30 import java.net.ProtocolException;
  31 import jdk.incubator.http.WebSocket;
  32 import java.nio.ByteBuffer;
  33 import java.util.concurrent.CompletableFuture;
  34 import java.util.concurrent.ExecutionException;
  35 import java.util.concurrent.Executors;
  36 import java.util.concurrent.TimeUnit;
  37 
  38 import static jdk.incubator.http.internal.websocket.TestSupport.Expectation.ifExpect;
  39 import static jdk.incubator.http.internal.websocket.TestSupport.assertCompletesExceptionally;
  40 import static jdk.incubator.http.internal.websocket.TestSupport.checkExpectations;
  41 import static org.testng.Assert.assertSame;
  42 
  43 /*
  44  * Examines sendPing/onPing contracts
  45  */
  46 public final class PingTest {
  47 
  48     /*
  49      * sendPing(message) is invoked. If the `message` argument is illegal, then
  50      * the method must throw an appropriate exception. Otherwise no exception
  51      * must be thrown.
  52      */
  53 //    @Test(dataProvider = "outgoingData", dataProviderClass = DataProviders.class)
  54     public void testSendPingArguments(ByteBuffer message) {
  55         WebSocket ws = newWebSocket();
  56         ifExpect(
  57                 message == null,
  58                 NullPointerException.class::isInstance)
  59         .orExpect(
  60                 message != null && message.remaining() > 125,
  61                 IllegalArgumentException.class::isInstance)
  62         .assertThrows(
  63                 () -> ws.sendPing(message)
  64         );
  65     }
  66 
  67     /*
  68      * sendPing(message) with a legal argument has been invoked, then:
  69      *
  70      * 1. A Ping message with the same payload appears on the wire
  71      * 2. The CF returned from the method completes normally with the same
  72      *    WebSocket that sendPing has been called on
  73      */
  74     @Test(dataProvider = "outgoingData", dataProviderClass = DataProviders.class)
  75     public void testSendPingWysiwyg(ByteBuffer message) throws ExecutionException, InterruptedException {
  76         if (message == null || message.remaining() > 125) {
  77             return;
  78         }
  79         ByteBuffer snapshot = copy(message);
  80         MockChannel channel = new MockChannel.Builder()
  81                 .expectPing(snapshot::equals)
  82                 .build();
  83         WebSocket ws = newWebSocket(channel);
  84         CompletableFuture<WebSocket> cf = ws.sendPing(message);
  85         WebSocket ws1 = cf.join();
  86         assertSame(ws1, ws); // (2)
  87         checkExpectations(channel); // (1)
  88     }
  89 
  90     /*
  91      * If an I/O error occurs while Ping messages is being sent, then:
  92      *
  93      * 1. The CF returned from sendPing completes exceptionally with this I/O
  94      *    error as the cause
  95      */
  96 //    @Test
  97     public void testSendPingIOException() {
  98         MockChannel ch = new MockChannel.Builder()
  99 //                .provideWriteException(IOException::new)
 100                 .build();
 101         WebSocket ws = newWebSocket(ch);
 102         CompletableFuture<WebSocket> cf = ws.sendPing(ByteBuffer.allocate(16));
 103         assertCompletesExceptionally(IOException.class, cf);
 104     }
 105 
 106     /*
 107      * If an incorrect Ping frame appears on the wire, then:
 108      *
 109      * 1. onError with the java.net.ProtocolException is invoked
 110      * 1. A Close frame with status code 1002 appears on the wire
 111      */
 112 //    @Test(dataProvider = "incorrectFrame", dataProviderClass = DataProviders.class)
 113     public void testOnPingIncorrect(boolean fin, boolean rsv1, boolean rsv2,
 114                                     boolean rsv3, ByteBuffer data) {
 115         if (fin && !rsv1 && !rsv2 && !rsv3 && data.remaining() <= 125) {
 116             throw new SkipException("Correct frame");
 117         }
 118         CompletableFuture<WebSocket> webSocket = new CompletableFuture<>();
 119         MockChannel channel = new MockChannel.Builder()
 120                 .provideFrame(fin, rsv1, rsv2, rsv3, Opcode.PING, data)
 121                 .expectClose((code, reason) ->
 122                         Integer.valueOf(1002).equals(code) && "".equals(reason))
 123                 .build();
 124         MockListener listener = new MockListener.Builder()
 125                 .expectOnOpen((ws) -> true)
 126                 .expectOnError((ws, error) -> error instanceof ProtocolException)
 127                 .build();
 128         webSocket.complete(newWebSocket(channel, listener));
 129         checkExpectations(500, TimeUnit.MILLISECONDS, channel, listener);
 130     }
 131 
 132     /*
 133      * If a Ping message has been read off the wire, then:
 134      *
 135      * 1. onPing is invoked with the data and the WebSocket the listener has
 136      *    been attached to
 137      * 2. A Pong message with the same contents will be sent in reply
 138      */
 139     @Test(dataProvider = "incomingData", dataProviderClass = DataProviders.class)
 140     public void testOnPingReply(ByteBuffer data) {
 141         CompletableFuture<WebSocket> webSocket = new CompletableFuture<>();
 142         MockChannel channel = new MockChannel.Builder()
 143                 .provideFrame(true, false, false, false, Opcode.PING, data)
 144                 .expectPong(data::equals)
 145                 .build();
 146         MockListener listener = new MockListener.Builder()
 147                 .expectOnOpen((ws) -> true) // maybe should capture with a CF?
 148                 .expectOnPing((ws, bb) -> data.equals(bb))
 149                 .build();
 150         webSocket.complete(newWebSocket(channel, listener));
 151         checkExpectations(500, TimeUnit.MILLISECONDS, channel, listener);
 152     }
 153 
 154     /*
 155      * If onPing throws an exception or CS returned from it completes
 156      * exceptionally, then:
 157      *
 158      * 1. onError is invoked with this particular exception as the cause and the
 159      *    WebSocket the listener has been attached to
 160      */
 161     public void testOnPingExceptions() {
 162     }
 163 
 164     /*
 165      * If a Ping message has been read off the wire and an I/O error occurs
 166      * while WebSocket sends a Pong reply to it, then:
 167      *
 168      * 1. onError is invoked with this error as the cause and the WebSocket this
 169      *    listener has been attached to
 170      */
 171     public void testOnPingReplyIOException() {
 172     }
 173 
 174     private WebSocket newWebSocket() {
 175         return newWebSocket(new MockChannel.Builder().build());
 176     }
 177 
 178     private WebSocket newWebSocket(RawChannel ch) {
 179         return newWebSocket(ch, new WebSocket.Listener() { });
 180     }
 181 
 182     private WebSocket newWebSocket(RawChannel ch, WebSocket.Listener l) {
 183 //        WebSocketImpl ws = new WebSocketImpl("", ch, l, Executors.newCachedThreadPool());
 184 //        ws.();
 185 //        ws.request(Long.MAX_VALUE);
 186         return null; // FIXME
 187     }
 188 
 189     public static ByteBuffer copy(ByteBuffer src) {
 190         int pos = src.position();
 191         ByteBuffer b = ByteBuffer.allocate(src.remaining()).put(src).flip();
 192         src.position(pos);
 193         return b;
 194     }
 195 }