• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 {
onMessage(ResponseBody message)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. */
setNextMessageDelegate(MessageDelegate delegate)43   public void setNextMessageDelegate(MessageDelegate delegate) {
44     this.delegate = delegate;
45   }
46 
onOpen(WebSocket webSocket, Response response)47   @Override public void onOpen(WebSocket webSocket, Response response) {
48   }
49 
onMessage(ResponseBody message)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 
onPing(Buffer buffer)62   @Override public void onPing(Buffer buffer) {
63     events.add(new Ping(buffer));
64   }
65 
onPong(Buffer buffer)66   @Override public void onPong(Buffer buffer) {
67     events.add(new Pong(buffer));
68   }
69 
onClose(int code, String reason)70   @Override public void onClose(int code, String reason) {
71     events.add(new Close(code, reason));
72   }
73 
onFailure(IOException e, Response response)74   @Override public void onFailure(IOException e, Response response) {
75     events.add(e);
76   }
77 
nextEvent()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 
assertTextMessage(String payload)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 
assertBinaryMessage(byte[] payload)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 
assertPing(Buffer payload)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 
assertPong(Buffer payload)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 
assertClose(int code, String reason)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 
assertFailure(Class<? extends IOException> cls, String message)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 
assertExhausted()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 
Message(MediaType mediaType)151     private Message(MediaType mediaType) {
152       this.mediaType = mediaType;
153     }
154 
toString()155     @Override public String toString() {
156       return "Message[" + mediaType + " " + buffer + "]";
157     }
158 
hashCode()159     @Override public int hashCode() {
160       return mediaType.hashCode() * 37 + buffer.hashCode();
161     }
162 
equals(Object obj)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 
Ping(Buffer buffer)175     private Ping(Buffer buffer) {
176       this.buffer = buffer;
177     }
178 
toString()179     @Override public String toString() {
180       return "Ping[" + buffer + "]";
181     }
182 
hashCode()183     @Override public int hashCode() {
184       return buffer.hashCode();
185     }
186 
equals(Object obj)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 
Pong(Buffer buffer)199     private Pong(Buffer buffer) {
200       this.buffer = buffer;
201     }
202 
toString()203     @Override public String toString() {
204       return "Pong[" + buffer + "]";
205     }
206 
hashCode()207     @Override public int hashCode() {
208       return buffer.hashCode();
209     }
210 
equals(Object obj)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 
Close(int code, String reason)224     private Close(int code, String reason) {
225       this.code = code;
226       this.reason = reason;
227     }
228 
toString()229     @Override public String toString() {
230       return "Close[" + code + " " + reason + "]";
231     }
232 
hashCode()233     @Override public int hashCode() {
234       return code * 37 + reason.hashCode();
235     }
236 
equals(Object obj)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