Home | History | Annotate | Download | only in ws
      1 /*
      2  * Copyright (C) 2014 Square, Inc.
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://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,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 package com.squareup.okhttp.ws;
     17 
     18 import com.squareup.okhttp.MediaType;
     19 import com.squareup.okhttp.Response;
     20 import com.squareup.okhttp.ResponseBody;
     21 import com.squareup.okhttp.internal.ws.WebSocketReader;
     22 import java.io.IOException;
     23 import java.util.concurrent.BlockingQueue;
     24 import java.util.concurrent.LinkedBlockingQueue;
     25 import java.util.concurrent.TimeUnit;
     26 import okio.Buffer;
     27 
     28 import static com.squareup.okhttp.ws.WebSocket.BINARY;
     29 import static com.squareup.okhttp.ws.WebSocket.TEXT;
     30 import static org.junit.Assert.assertEquals;
     31 import static org.junit.Assert.assertNotNull;
     32 import static org.junit.Assert.assertTrue;
     33 
     34 public final class WebSocketRecorder implements WebSocketReader.FrameCallback, WebSocketListener {
     35   public interface MessageDelegate {
     36     void onMessage(ResponseBody message) throws IOException;
     37   }
     38 
     39   private final BlockingQueue<Object> events = new LinkedBlockingQueue<>();
     40   private MessageDelegate delegate;
     41 
     42   /** Sets a delegate for the next call to {@link #onMessage}. Cleared after invoked. */
     43   public void setNextMessageDelegate(MessageDelegate delegate) {
     44     this.delegate = delegate;
     45   }
     46 
     47   @Override public void onOpen(WebSocket webSocket, Response response) {
     48   }
     49 
     50   @Override public void onMessage(ResponseBody message) throws IOException {
     51     if (delegate != null) {
     52       delegate.onMessage(message);
     53       delegate = null;
     54     } else {
     55       Message event = new Message(message.contentType());
     56       message.source().readAll(event.buffer);
     57       message.close();
     58       events.add(event);
     59     }
     60   }
     61 
     62   @Override public void onPing(Buffer buffer) {
     63     events.add(new Ping(buffer));
     64   }
     65 
     66   @Override public void onPong(Buffer buffer) {
     67     events.add(new Pong(buffer));
     68   }
     69 
     70   @Override public void onClose(int code, String reason) {
     71     events.add(new Close(code, reason));
     72   }
     73 
     74   @Override public void onFailure(IOException e, Response response) {
     75     events.add(e);
     76   }
     77 
     78   private Object nextEvent() {
     79     try {
     80       Object event = events.poll(10, TimeUnit.SECONDS);
     81       if (event == null) {
     82         throw new AssertionError("Timed out.");
     83       }
     84       return event;
     85     } catch (InterruptedException e) {
     86       throw new AssertionError(e);
     87     }
     88   }
     89 
     90   public void assertTextMessage(String payload) throws IOException {
     91     Message message = new Message(TEXT);
     92     message.buffer.writeUtf8(payload);
     93     Object actual = nextEvent();
     94     if (actual instanceof IOException) {
     95       throw (IOException) actual;
     96     }
     97     assertEquals(message, actual);
     98   }
     99 
    100   public void assertBinaryMessage(byte[] payload) throws IOException {
    101     Message message = new Message(BINARY);
    102     message.buffer.write(payload);
    103     Object actual = nextEvent();
    104     if (actual instanceof IOException) {
    105       throw (IOException) actual;
    106     }
    107     assertEquals(message, actual);
    108   }
    109 
    110   public void assertPing(Buffer payload) throws IOException {
    111     Object actual = nextEvent();
    112     if (actual instanceof IOException) {
    113       throw (IOException) actual;
    114     }
    115     assertEquals(new Ping(payload), actual);
    116   }
    117 
    118   public void assertPong(Buffer payload) throws IOException {
    119     Object actual = nextEvent();
    120     if (actual instanceof IOException) {
    121       throw (IOException) actual;
    122     }
    123     assertEquals(new Pong(payload), actual);
    124   }
    125 
    126   public void assertClose(int code, String reason) throws IOException {
    127     Object actual = nextEvent();
    128     if (actual instanceof IOException) {
    129       throw (IOException) actual;
    130     }
    131     assertEquals(new Close(code, reason), actual);
    132   }
    133 
    134   public void assertFailure(Class<? extends IOException> cls, String message) {
    135     Object event = nextEvent();
    136     String errorMessage =
    137         "Expected [" + cls.getName() + ": " + message + "] but was [" + event + "].";
    138     assertNotNull(errorMessage, event);
    139     assertEquals(errorMessage, cls, event.getClass());
    140     assertEquals(errorMessage, cls.cast(event).getMessage(), message);
    141   }
    142 
    143   public void assertExhausted() {
    144     assertTrue("Remaining events: " + events, events.isEmpty());
    145   }
    146 
    147   private static class Message {
    148     public final MediaType mediaType;
    149     public final Buffer buffer = new Buffer();
    150 
    151     private Message(MediaType mediaType) {
    152       this.mediaType = mediaType;
    153     }
    154 
    155     @Override public String toString() {
    156       return "Message[" + mediaType + " " + buffer + "]";
    157     }
    158 
    159     @Override public int hashCode() {
    160       return mediaType.hashCode() * 37 + buffer.hashCode();
    161     }
    162 
    163     @Override public boolean equals(Object obj) {
    164       if (obj instanceof Message) {
    165         Message other = (Message) obj;
    166         return mediaType.equals(other.mediaType) && buffer.equals(other.buffer);
    167       }
    168       return false;
    169     }
    170   }
    171 
    172   private static class Ping {
    173     public final Buffer buffer;
    174 
    175     private Ping(Buffer buffer) {
    176       this.buffer = buffer;
    177     }
    178 
    179     @Override public String toString() {
    180       return "Ping[" + buffer + "]";
    181     }
    182 
    183     @Override public int hashCode() {
    184       return buffer.hashCode();
    185     }
    186 
    187     @Override public boolean equals(Object obj) {
    188       if (obj instanceof Ping) {
    189         Ping other = (Ping) obj;
    190         return buffer == null ? other.buffer == null : buffer.equals(other.buffer);
    191       }
    192       return false;
    193     }
    194   }
    195 
    196   private static class Pong {
    197     public final Buffer buffer;
    198 
    199     private Pong(Buffer buffer) {
    200       this.buffer = buffer;
    201     }
    202 
    203     @Override public String toString() {
    204       return "Pong[" + buffer + "]";
    205     }
    206 
    207     @Override public int hashCode() {
    208       return buffer.hashCode();
    209     }
    210 
    211     @Override public boolean equals(Object obj) {
    212       if (obj instanceof Pong) {
    213         Pong other = (Pong) obj;
    214         return buffer == null ? other.buffer == null : buffer.equals(other.buffer);
    215       }
    216       return false;
    217     }
    218   }
    219 
    220   private static class Close {
    221     public final int code;
    222     public final String reason;
    223 
    224     private Close(int code, String reason) {
    225       this.code = code;
    226       this.reason = reason;
    227     }
    228 
    229     @Override public String toString() {
    230       return "Close[" + code + " " + reason + "]";
    231     }
    232 
    233     @Override public int hashCode() {
    234       return code * 37 + reason.hashCode();
    235     }
    236 
    237     @Override public boolean equals(Object obj) {
    238       if (obj instanceof Close) {
    239         Close other = (Close) obj;
    240         return code == other.code && reason.equals(other.reason);
    241       }
    242       return false;
    243     }
    244   }
    245 }
    246