Merge pull request #4418 from kumpera/fix_gclass_recording_on_failure
[mono.git] / mcs / class / System / Test / System.Net.WebSockets / ClientWebSocketTest.cs
1 using System;
2 using System.Net;
3 using System.Threading;
4 using System.Threading.Tasks;
5 using System.Collections.Generic;
6 using System.Net.WebSockets;
7 using System.Reflection;
8 using System.Text;
9
10 using NUnit.Framework;
11
12 using MonoTests.Helpers;
13
14 namespace MonoTests.System.Net.WebSockets
15 {
16         [TestFixture]
17         public class ClientWebSocketTest
18         {
19                 const string EchoServerUrl = "ws://corefx-net.cloudapp.net/WebSocket/EchoWebSocket.ashx";
20
21                 ClientWebSocket socket;
22                 MethodInfo headerSetMethod;
23                 int Port;
24
25                 [SetUp]
26                 public void Setup ()
27                 {
28                         socket = new ClientWebSocket ();
29                         Port = NetworkHelpers.FindFreePort ();
30                 }
31
32                 HttpListener _listener;
33                 HttpListener listener {
34                         get {
35                                 if (_listener != null)
36                                         return _listener;
37
38                                 var tmp = new HttpListener ();
39                                 tmp.Prefixes.Add ("http://localhost:" + Port + "/");
40                                 tmp.Start ();
41                                 return _listener = tmp;
42                         }
43                 }
44
45                 [TearDown]
46                 public void Teardown ()
47                 {
48                         if (_listener != null) {
49                                 _listener.Stop ();
50                                 _listener = null;
51                         }
52                         if (socket != null) {
53                                 if (socket.State == WebSocketState.Open)
54                                         socket.CloseAsync (WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None).Wait (2000);
55                                 socket.Dispose ();
56                         }
57                 }
58
59                 [Test]
60                 [Category ("MobileNotWorking")] // Fails when ran as part of the entire BCL test suite. Works when only this fixture is ran
61                 public void ServerHandshakeReturnCrapStatusCodeTest ()
62                 {
63                         // On purpose, 
64                         #pragma warning disable 4014
65                         HandleHttpRequestAsync ((req, resp) => resp.StatusCode = 418);
66                         #pragma warning restore 4014
67                         try {
68                                 Assert.IsTrue (socket.ConnectAsync (new Uri ("ws://localhost:" + Port), CancellationToken.None).Wait (5000));
69                         } catch (AggregateException e) {
70                                 AssertWebSocketException (e, WebSocketError.Success, typeof (WebException));
71                                 return;
72                         }
73                         Assert.Fail ("Should have thrown");
74                 }
75
76                 [Test]
77                 [Category ("MobileNotWorking")] // Fails when ran as part of the entire BCL test suite. Works when only this fixture is ran
78                 public void ServerHandshakeReturnWrongUpgradeHeader ()
79                 {
80                         #pragma warning disable 4014
81                         HandleHttpRequestAsync ((req, resp) => {
82                                         resp.StatusCode = 101;
83                                         resp.Headers["Upgrade"] = "gtfo";
84                                 });
85                         #pragma warning restore 4014
86                         try {
87                                 Assert.IsTrue (socket.ConnectAsync (new Uri ("ws://localhost:" + Port), CancellationToken.None).Wait (5000));
88                         } catch (AggregateException e) {
89                                 AssertWebSocketException (e, WebSocketError.Success);
90                                 return;
91                         }
92                         Assert.Fail ("Should have thrown");
93                 }
94
95                 [Test]
96                 [Category ("MobileNotWorking")] // Fails when ran as part of the entire BCL test suite. Works when only this fixture is ran
97                 public void ServerHandshakeReturnWrongConnectionHeader ()
98                 {
99                         #pragma warning disable 4014
100                         HandleHttpRequestAsync ((req, resp) => {
101                                         resp.StatusCode = 101;
102                                         resp.Headers["Upgrade"] = "websocket";
103                                         // Mono http request doesn't like the forcing, test still valid since the default connection header value is empty
104                                         //ForceSetHeader (resp.Headers, "Connection", "Foo");
105                                 });
106                         #pragma warning restore 4014
107                         try {
108                                 Assert.IsTrue (socket.ConnectAsync (new Uri ("ws://localhost:" + Port), CancellationToken.None).Wait (5000));
109                         } catch (AggregateException e) {
110                                 AssertWebSocketException (e, WebSocketError.Success);
111                                 return;
112                         }
113                         Assert.Fail ("Should have thrown");
114                 }
115
116                 [Test]
117                 [Category ("MobileNotWorking")] // The test hangs when ran as part of the entire BCL test suite. Works when only this fixture is ran
118                 public void EchoTest ()
119                 {
120                         const string Payload = "This is a websocket test";
121
122                         Assert.AreEqual (WebSocketState.None, socket.State);
123                         socket.ConnectAsync (new Uri (EchoServerUrl), CancellationToken.None).Wait ();
124                         Assert.AreEqual (WebSocketState.Open, socket.State);
125
126                         var sendBuffer = Encoding.ASCII.GetBytes (Payload);
127                         Assert.IsTrue (socket.SendAsync (new ArraySegment<byte> (sendBuffer), WebSocketMessageType.Text, true, CancellationToken.None).Wait (5000));
128
129                         var receiveBuffer = new byte[Payload.Length];
130                         var resp = socket.ReceiveAsync (new ArraySegment<byte> (receiveBuffer), CancellationToken.None).Result;
131
132                         Assert.AreEqual (Payload.Length, resp.Count);
133                         Assert.IsTrue (resp.EndOfMessage);
134                         Assert.AreEqual (WebSocketMessageType.Text, resp.MessageType);
135                         Assert.AreEqual (Payload, Encoding.ASCII.GetString (receiveBuffer, 0, resp.Count));
136
137                         Assert.IsTrue (socket.CloseAsync (WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None).Wait (5000));
138                         Assert.AreEqual (WebSocketState.Closed, socket.State);
139                 }
140
141                 [Test]
142                 [Category ("MobileNotWorking")] // Fails when ran as part of the entire BCL test suite. Works when only this fixture is ran
143                 public void CloseOutputAsyncTest ()
144                 {
145                         Assert.IsTrue (socket.ConnectAsync (new Uri (EchoServerUrl), CancellationToken.None).Wait (5000));
146                         Assert.AreEqual (WebSocketState.Open, socket.State);
147
148                         Assert.IsTrue (socket.CloseOutputAsync (WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None).Wait (5000));
149                         Assert.AreEqual (WebSocketState.CloseSent, socket.State);
150
151                         var resp = socket.ReceiveAsync (new ArraySegment<byte> (new byte[0]), CancellationToken.None).Result;
152                         Assert.AreEqual (WebSocketState.Closed, socket.State);
153                         Assert.AreEqual (WebSocketMessageType.Close, resp.MessageType);
154                         Assert.AreEqual (WebSocketCloseStatus.NormalClosure, resp.CloseStatus);
155                         Assert.AreEqual (string.Empty, resp.CloseStatusDescription);
156                 }
157
158                 [Test]
159                 [Category ("MobileNotWorking")] // Fails when ran as part of the entire BCL test suite. Works when only this fixture is ran
160                 public void CloseAsyncTest ()
161                 {
162                         if (!socket.ConnectAsync (new Uri (EchoServerUrl), CancellationToken.None).Wait (5000)) {
163                                 Assert.Inconclusive (socket.State.ToString ());
164                                 return;
165                         }
166
167                         Assert.AreEqual (WebSocketState.Open, socket.State);
168
169                         Assert.IsTrue (socket.CloseAsync (WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None).Wait (5000));
170                         Assert.AreEqual (WebSocketState.Closed, socket.State);
171                 }
172
173                 [Test]
174                 [ExpectedException (typeof (InvalidOperationException))]
175                 [Category ("MobileNotWorking")] // Fails when ran as part of the entire BCL test suite. Works when only this fixture is ran
176                 public void SendAsyncArgTest_NotConnected ()
177                 {
178                         socket.SendAsync (new ArraySegment<byte> (new byte[0]), WebSocketMessageType.Text, true, CancellationToken.None);
179                 }
180
181                 [Test, ExpectedException (typeof (ArgumentNullException))]
182                 [Category ("MobileNotWorking")] // Fails when ran as part of the entire BCL test suite. Works when only this fixture is ran
183                 public void SendAsyncArgTest_NoArray ()
184                 {
185                         Assert.IsTrue (socket.ConnectAsync (new Uri (EchoServerUrl), CancellationToken.None).Wait (5000));
186                         socket.SendAsync (new ArraySegment<byte> (), WebSocketMessageType.Text, true, CancellationToken.None);
187                 }
188
189                 [Test]
190                 [ExpectedException (typeof (InvalidOperationException))]
191                 [Category ("MobileNotWorking")] // Fails when ran as part of the entire BCL test suite. Works when only this fixture is ran
192                 public void ReceiveAsyncArgTest_NotConnected ()
193                 {
194                         socket.ReceiveAsync (new ArraySegment<byte> (new byte[0]), CancellationToken.None);
195                 }
196
197                 [Test, ExpectedException (typeof (ArgumentNullException))]
198                 [Category ("MobileNotWorking")] // Fails when ran as part of the entire BCL test suite. Works when only this fixture is ran
199                 public void ReceiveAsyncArgTest_NoArray ()
200                 {
201                         Assert.IsTrue (socket.ConnectAsync (new Uri (EchoServerUrl), CancellationToken.None).Wait (5000));
202                         socket.ReceiveAsync (new ArraySegment<byte> (), CancellationToken.None);
203                 }
204
205                 [Test]
206                 [Category ("MobileNotWorking")] // Fails when ran as part of the entire BCL test suite. Works when only this fixture is ran
207                 public void ReceiveAsyncWrongState_Closed ()
208                 {
209                         try {
210                                 Assert.IsTrue (socket.ConnectAsync (new Uri (EchoServerUrl), CancellationToken.None).Wait (5000));
211                                 Assert.IsTrue (socket.CloseAsync (WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None).Wait (5000));
212                                 Assert.IsTrue (socket.ReceiveAsync (new ArraySegment<byte> (new byte[0]), CancellationToken.None).Wait (5000));
213                         } catch (AggregateException e) {
214                                 AssertWebSocketException (e, WebSocketError.Success);
215                                 return;
216                         }
217                         Assert.Fail ("Should have thrown");
218                 }
219
220                 [Test]
221                 [Category ("MobileNotWorking")] // Fails when ran as part of the entire BCL test suite. Works when only this fixture is ran
222                 public void SendAsyncWrongState_Closed ()
223                 {
224                         try {
225                                 Assert.IsTrue (socket.ConnectAsync (new Uri (EchoServerUrl), CancellationToken.None).Wait (5000));
226                                 Assert.IsTrue (socket.CloseAsync (WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None).Wait (5000));
227                                 Assert.IsTrue (socket.SendAsync (new ArraySegment<byte> (new byte[0]), WebSocketMessageType.Text, true, CancellationToken.None).Wait (5000));
228                         } catch (AggregateException e) {
229                                 AssertWebSocketException (e, WebSocketError.Success);
230                                 return;
231                         }
232                         Assert.Fail ("Should have thrown");
233                 }
234
235                 [Test]
236                 [Category ("MobileNotWorking")] // Fails when ran as part of the entire BCL test suite. Works when only this fixture is ran
237                 public void SendAsyncWrongState_CloseSent ()
238                 {
239                         try {
240                                 Assert.IsTrue (socket.ConnectAsync (new Uri (EchoServerUrl), CancellationToken.None).Wait (5000));
241                                 Assert.IsTrue (socket.CloseOutputAsync (WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None).Wait (5000));
242                                 Assert.IsTrue (socket.SendAsync (new ArraySegment<byte> (new byte[0]), WebSocketMessageType.Text, true, CancellationToken.None).Wait (5000));
243                         } catch (AggregateException e) {
244                                 AssertWebSocketException (e, WebSocketError.Success);
245                                 return;
246                         }
247                         Assert.Fail ("Should have thrown");
248                 }
249
250                 [Test]
251                 [Category ("NotWorking")]  // FIXME: test relies on unimplemented HttpListenerContext.AcceptWebSocketAsync (), reenable it when the method is implemented
252                 public void SendAsyncEndOfMessageTest ()
253                 {
254                         var cancellationToken = new CancellationTokenSource (TimeSpan.FromSeconds (30)).Token;
255                         SendAsyncEndOfMessageTest (false, WebSocketMessageType.Text, cancellationToken).Wait (5000);
256                         SendAsyncEndOfMessageTest (true, WebSocketMessageType.Text, cancellationToken).Wait (5000);
257                         SendAsyncEndOfMessageTest (false, WebSocketMessageType.Binary, cancellationToken).Wait (5000);
258                         SendAsyncEndOfMessageTest (true, WebSocketMessageType.Binary, cancellationToken).Wait (5000);
259                 }
260
261                 public async Task SendAsyncEndOfMessageTest (bool expectedEndOfMessage, WebSocketMessageType webSocketMessageType, CancellationToken cancellationToken)
262                 {
263                         using (var client = new ClientWebSocket ()) {
264                                 // Configure the listener.
265                                 var serverReceive = HandleHttpWebSocketRequestAsync<WebSocketReceiveResult> (async socket => await socket.ReceiveAsync (new ArraySegment<byte> (new byte[32]), cancellationToken), cancellationToken);
266
267                                 // Connect to the listener and make the request.
268                                 await client.ConnectAsync (new Uri ("ws://localhost:" + Port + "/"), cancellationToken);
269                                 await client.SendAsync (new ArraySegment<byte> (Encoding.UTF8.GetBytes ("test")), webSocketMessageType, expectedEndOfMessage, cancellationToken);
270
271                                 // Wait for the listener to handle the request and return its result.
272                                 var result = await serverReceive;
273
274                                 // Cleanup and check results.
275                                 await client.CloseAsync (WebSocketCloseStatus.NormalClosure, "Finished", cancellationToken);
276                                 Assert.AreEqual (expectedEndOfMessage, result.EndOfMessage, "EndOfMessage should be " + expectedEndOfMessage);
277                         }
278                 }
279
280                 async Task<T> HandleHttpWebSocketRequestAsync<T> (Func<WebSocket, Task<T>> action, CancellationToken cancellationToken)
281                 {
282                         var ctx = await this.listener.GetContextAsync ();
283                         var wsContext = await ctx.AcceptWebSocketAsync (null);
284                         var result = await action (wsContext.WebSocket);
285                         await wsContext.WebSocket.CloseOutputAsync (WebSocketCloseStatus.NormalClosure, "Finished", cancellationToken);
286                         return result;
287                 }
288
289                 async Task HandleHttpRequestAsync (Action<HttpListenerRequest, HttpListenerResponse> handler)
290                 {
291                         var ctx = await listener.GetContextAsync ();
292                         handler (ctx.Request, ctx.Response);
293                         ctx.Response.Close ();
294                 }
295
296                 void AssertWebSocketException (AggregateException e, WebSocketError error, Type inner = null)
297                 {
298                         var wsEx = e.InnerException as WebSocketException;
299                         Console.WriteLine (e.InnerException.ToString ());
300                         Assert.IsNotNull (wsEx, "Not a websocketexception");
301                         Assert.AreEqual (error, wsEx.WebSocketErrorCode);
302                         if (inner != null) {
303                                 Assert.IsNotNull (wsEx.InnerException);
304                                 Assert.IsTrue (inner.IsInstanceOfType (wsEx.InnerException));
305                         }
306                 }
307
308                 void ForceSetHeader (WebHeaderCollection headers, string name, string value)
309                 {
310                         if (headerSetMethod == null)
311                                 headerSetMethod = typeof (WebHeaderCollection).GetMethod ("AddValue", BindingFlags.NonPublic);
312                         headerSetMethod.Invoke (headers, new[] { name, value });
313                 }
314         }
315 }
316