Index: tests/standalone/io/raw_synchronous_socket_test.dart |
diff --git a/tests/standalone/io/raw_synchronous_socket_test.dart b/tests/standalone/io/raw_synchronous_socket_test.dart |
new file mode 100644 |
index 0000000000000000000000000000000000000000..28236b987183d7eaa23904936d7b60798302f922 |
--- /dev/null |
+++ b/tests/standalone/io/raw_synchronous_socket_test.dart |
@@ -0,0 +1,493 @@ |
+// Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file |
+// for details. All rights reserved. Use of this source code is governed by a |
+// BSD-style license that can be found in the LICENSE file. |
+ |
+import "dart:async"; |
+import "dart:io"; |
+import "dart:isolate"; |
+import "dart:math"; |
+ |
+import "package:async_helper/async_helper.dart"; |
+import "package:expect/expect.dart"; |
+ |
+const String LOOPBACK_IP_V4_STRING = "127.0.0.1"; |
+ |
+void testArguments() { |
+ Expect.throws(() => RawSynchronousSocket.connectSync(null, 0)); |
+ Expect.throws( |
+ () => RawSynchronousSocket.connectSync(LOOPBACK_IP_V4_STRING, null)); |
+ Expect.throws( |
+ () => RawSynchronousSocket.connectSync(LOOPBACK_IP_V4_STRING, 65536)); |
+ Expect.throws( |
+ () => RawSynchronousSocket.connectSync(LOOPBACK_IP_V4_STRING, -1)); |
+ Expect.throws(() => |
+ RawSynchronousSocket.connectSync(LOOPBACK_IP_V4_STRING, 0, backlog: -1)); |
+} |
+ |
+// The connection attempt happens on the main Dart thread and the OS timeout can |
+// be arbitrarily long, causing timeout issues on the build bots. This isn't an |
+// issue with the async sockets since the lookup for a connect call happens on |
+// the IO service thread. |
+/* |
+void testInvalidConnect() { |
+ // Connect to an unknown DNS name. |
+ try { |
+ var socket = RawSynchronousSocket.connectSync("ko.faar.__hest__", 0); |
+ Expect.fail("Failure expected"); |
+ } catch (e) { |
+ Expect.isTrue(e is SocketException); |
+ } |
+ |
+ // Connect to an unavaliable IP-address. |
+ try { |
+ var socket = RawSynchronousSocket.connectSync("1.2.3.4", 0); |
+ Expect.fail("Failure expected"); |
+ } catch (e) { |
+ Expect.isTrue(e is SocketException); |
+ } |
+} |
+*/ |
+ |
+void testSimpleConnect() { |
+ asyncStart(); |
+ RawServerSocket.bind(InternetAddress.LOOPBACK_IP_V4, 0).then((server) { |
+ var socket = |
+ RawSynchronousSocket.connectSync(LOOPBACK_IP_V4_STRING, server.port); |
+ server.listen((serverSocket) { |
+ Expect.equals(socket.address, serverSocket.remoteAddress); |
+ Expect.equals(socket.port, serverSocket.remotePort); |
+ Expect.equals(socket.remoteAddress, server.address); |
+ Expect.equals(socket.remotePort, server.port); |
+ socket.closeSync(); |
+ server.close(); |
+ asyncEnd(); |
+ }); |
+ }); |
+} |
+ |
+void testServerListenAfterConnect() { |
+ asyncStart(); |
+ RawServerSocket.bind(InternetAddress.LOOPBACK_IP_V4, 0).then((server) { |
+ Expect.isTrue(server.port > 0); |
+ var client = |
+ RawSynchronousSocket.connectSync(LOOPBACK_IP_V4_STRING, server.port); |
+ server.listen((socket) { |
+ client.closeSync(); |
+ server.close(); |
+ socket.close(); |
+ asyncEnd(); |
+ }); |
+ }); |
+} |
+ |
+const messageSize = 1000; |
+// Configuration fields for the EchoServer. |
+enum EchoServerTypes { |
+ // Max accumulated connections to server before close. Defaults to 1. |
+ CONNECTION_COUNT, |
+ // Sets the range of the fields to check in the list generated by |
+ // createTestData(). |
+ OFFSET_END, |
+ OFFSET_START, |
+ // The port used to communicate with an isolate. |
+ ISOLATE_SEND_PORT, |
+ // The port of the newly created echo server. |
+ SERVER_PORT |
+} |
+ |
+List<int> createTestData() { |
+ return new List<int>.generate(messageSize, (index) => index & 0xff); |
+} |
+ |
+// Consumes data generated by a test and compares it against the original test |
+// data. The optional fields, start and end, are used to compare against |
+// segments of the original test data list. In other words, data.length == (end |
+// - start). |
+void verifyTestData(List<int> data, [int start = 0, int end]) { |
+ assert(data != null); |
+ List<int> expected = createTestData(); |
+ if (end == null) { |
+ end = data.length; |
+ } |
+ end = min(messageSize, end); |
+ Expect.equals(end - start, data.length); |
+ for (int i = 0; i < (end - start); i++) { |
+ Expect.equals(expected[start + i], data[i]); |
+ } |
+} |
+ |
+// The echo server is spawned in a new isolate and is used to test various |
+// synchronous read/write operations by echoing any data received back to the |
+// sender. The server should shutdown automatically after a specified number of |
+// socket disconnections (default: 1). |
+Future echoServer(var sendPort) async { |
+ RawServerSocket.bind(InternetAddress.LOOPBACK_IP_V4, 0).then((server) async { |
+ ReceivePort receivePort = new ReceivePort(); |
+ Map response = { |
+ EchoServerTypes.ISOLATE_SEND_PORT: receivePort.sendPort, |
+ EchoServerTypes.SERVER_PORT: server.port |
+ }; |
+ sendPort.send(response); |
+ Map limits = await receivePort.first; |
+ int start = limits[EchoServerTypes.OFFSET_START]; |
+ int end = limits[EchoServerTypes.OFFSET_END]; |
+ int length = end - start; |
+ int connection_count = limits[EchoServerTypes.CONNECTION_COUNT] ?? 1; |
+ int connections = 0; |
+ sendPort = limits[EchoServerTypes.ISOLATE_SEND_PORT]; |
+ server.listen((client) { |
+ int bytesRead = 0; |
+ int bytesWritten = 0; |
+ bool closedEventReceived = false; |
+ List<int> data = new List<int>(length); |
+ client.writeEventsEnabled = false; |
+ client.listen((event) { |
+ switch (event) { |
+ case RawSocketEvent.READ: |
+ Expect.isTrue(bytesWritten == 0); |
+ Expect.isTrue(client.available() > 0); |
+ var buffer = client.read(client.available()); |
+ data.setRange(bytesRead, bytesRead + buffer.length, buffer); |
+ bytesRead += buffer.length; |
+ // Once we've read all the data, we can echo it back. Otherwise, |
+ // keep waiting for more bytes. |
+ if (bytesRead >= length) { |
+ verifyTestData(data, start, end); |
+ client.writeEventsEnabled = true; |
+ } |
+ break; |
+ case RawSocketEvent.WRITE: |
+ Expect.isFalse(client.writeEventsEnabled); |
+ bytesWritten += |
+ client.write(data, bytesWritten, data.length - bytesWritten); |
+ if (bytesWritten < length) { |
+ client.writeEventsEnabled = true; |
+ } else if (bytesWritten == length) { |
+ // Close the socket for writing from the server since we're done |
+ // writing to this socket. The connection is closed completely |
+ // after the client closes the socket for reading from the server. |
+ client.shutdown(SocketDirection.SEND); |
+ } |
+ break; |
+ case RawSocketEvent.READ_CLOSED: |
+ client.close(); |
+ break; |
+ case RawSocketEvent.CLOSED: |
+ Expect.isFalse(closedEventReceived); |
+ closedEventReceived = true; |
+ break; |
+ default: |
+ throw "Unexpected event $event"; |
+ } |
+ }, onDone: () { |
+ Expect.isTrue(closedEventReceived); |
+ connections++; |
+ if (connections >= connection_count) { |
+ server.close(); |
+ } |
+ }); |
+ }, onDone: () { |
+ // Let the client know we're shutting down then kill the isolate. |
+ sendPort.send(null); |
+ kill(); |
+ }); |
+ }); |
+} |
+ |
+Future testSimpleReadWrite({bool dropReads}) async { |
+ asyncStart(); |
+ // This test creates a server and a client connects. The client writes data |
+ // to the socket and the server echos it back. The client confirms the data it |
+ // reads is the same as the data sent, then closes the socket, resulting in |
+ // the closing of the server, which responds on receivePort with null to |
+ // specify the echo server isolate is about to be killed. If an error occurs |
+ // in the echo server, the exception and stack trace are sent to receivePort, |
+ // which prints the exception and stack trace before eventually throwing an |
+ // error. |
+ ReceivePort receivePort = new ReceivePort(); |
+ Isolate echo = await Isolate.spawn(echoServer, receivePort.sendPort); |
+ |
+ Map response = await receivePort.first; |
+ SendPort sendPort = response[EchoServerTypes.ISOLATE_SEND_PORT]; |
+ int serverInternetPort = response[EchoServerTypes.SERVER_PORT]; |
+ |
+ receivePort = new ReceivePort(); |
+ echo.addErrorListener(receivePort.sendPort); |
+ |
+ Map limits = { |
+ EchoServerTypes.OFFSET_START: 0, |
+ EchoServerTypes.OFFSET_END: messageSize, |
+ EchoServerTypes.ISOLATE_SEND_PORT: receivePort.sendPort |
+ }; |
+ sendPort.send(limits); |
+ |
+ try { |
+ var socket = RawSynchronousSocket.connectSync( |
+ LOOPBACK_IP_V4_STRING, serverInternetPort); |
+ List<int> data = createTestData(); |
+ socket.writeFromSync(data); |
+ List<int> result = socket.readSync(data.length); |
+ verifyTestData(result); |
+ socket.shutdown(SocketDirection.SEND); |
+ socket.closeSync(); |
+ } catch (e, stack) { |
+ print("Echo test failed in the client"); |
+ rethrow; |
+ } |
+ // Wait for the server to shutdown before finishing the test. |
+ var result = await receivePort.first; |
+ if (result != null) { |
+ throw "Echo test failed in server!\nError: ${result[0]}\nStack trace:" + |
+ " ${result[1]}"; |
+ } |
+ asyncEnd(); |
+} |
+ |
+Future testPartialRead() async { |
+ asyncStart(); |
+ // This test is based on testSimpleReadWrite, but instead of reading the |
+ // entire echoed message at once, it reads it in two calls to readIntoSync. |
+ ReceivePort receivePort = new ReceivePort(); |
+ Isolate echo = await Isolate.spawn(echoServer, receivePort.sendPort); |
+ |
+ Map response = await receivePort.first; |
+ SendPort sendPort = response[EchoServerTypes.ISOLATE_SEND_PORT]; |
+ int serverInternetPort = response[EchoServerTypes.SERVER_PORT]; |
+ List<int> data = createTestData(); |
+ |
+ receivePort = new ReceivePort(); |
+ echo.addErrorListener(receivePort.sendPort); |
+ |
+ Map limits = { |
+ EchoServerTypes.OFFSET_START: 0, |
+ EchoServerTypes.OFFSET_END: 1000, |
+ EchoServerTypes.ISOLATE_SEND_PORT: receivePort.sendPort |
+ }; |
+ sendPort.send(limits); |
+ |
+ try { |
+ var socket = RawSynchronousSocket.connectSync( |
+ LOOPBACK_IP_V4_STRING, serverInternetPort); |
+ int half_length = (data.length / 2).toInt(); |
+ |
+ // Send the full data list to the server. |
+ socket.writeFromSync(data); |
+ List<int> result = new List<int>(data.length); |
+ |
+ // Read half at a time and check that there's still more bytes available. |
+ socket.readIntoSync(result, 0, half_length); |
+ verifyTestData(result.sublist(0, half_length), 0, half_length); |
+ Expect.isTrue(socket.available() == (data.length - half_length)); |
+ |
+ // Read the second half and verify again. |
+ socket.readIntoSync(result, half_length); |
+ verifyTestData(result); |
+ Expect.isTrue(socket.available() == 0); |
+ |
+ socket.closeSync(); |
+ } catch (e, stack) { |
+ print("Echo test failed in the client."); |
+ rethrow; |
+ } |
+ // Wait for the server to shutdown before finishing the test. |
+ var result = await receivePort.first; |
+ if (result != null) { |
+ throw "Echo test failed in server!\nError: ${result[0]}\nStack trace:" + |
+ " ${result[1]}"; |
+ } |
+ asyncEnd(); |
+} |
+ |
+Future testPartialWrite() async { |
+ asyncStart(); |
+ // This test is based on testSimpleReadWrite, but instead of writing the |
+ // entire data buffer at once, it writes different parts of the buffer over |
+ // multiple calls to writeFromSync. |
+ ReceivePort receivePort = new ReceivePort(); |
+ Isolate echo = await Isolate.spawn(echoServer, receivePort.sendPort); |
+ |
+ Map response = await receivePort.first; |
+ List<int> data = createTestData(); |
+ SendPort sendPort = response[EchoServerTypes.ISOLATE_SEND_PORT]; |
+ int startOffset = 32; |
+ int endOffset = (data.length / 2).toInt(); |
+ int serverInternetPort = response[EchoServerTypes.SERVER_PORT]; |
+ |
+ receivePort = new ReceivePort(); |
+ echo.addErrorListener(receivePort.sendPort); |
+ |
+ Map limits = { |
+ EchoServerTypes.OFFSET_START: startOffset, |
+ EchoServerTypes.OFFSET_END: endOffset, |
+ EchoServerTypes.ISOLATE_SEND_PORT: receivePort.sendPort |
+ }; |
+ sendPort.send(limits); |
+ try { |
+ var socket = RawSynchronousSocket.connectSync( |
+ LOOPBACK_IP_V4_STRING, serverInternetPort); |
+ List<int> data = createTestData(); |
+ |
+ // Write a subset of data to the server. |
+ socket.writeFromSync(data, startOffset, endOffset); |
+ |
+ // Grab the response and verify it's correct. |
+ List<int> result = new List<int>(endOffset - startOffset); |
+ socket.readIntoSync(result); |
+ |
+ Expect.equals(result.length, endOffset - startOffset); |
+ verifyTestData(result, startOffset, endOffset); |
+ socket.closeSync(); |
+ } catch (e, stack) { |
+ print("Echo test failed in the client."); |
+ rethrow; |
+ } |
+ |
+ // Wait for the server to shutdown before finishing the test. |
+ var result = await receivePort.first; |
+ if (result != null) { |
+ throw "Echo test failed in server!\nError: ${result[0]}\nStack trace:" + |
+ " ${result[1]}"; |
+ } |
+ asyncEnd(); |
+} |
+ |
+Future testShutdown() async { |
+ asyncStart(); |
+ // This test creates a server and a client connects. The client then tries to |
+ // perform various operations after being shutdown in a specific direction, to |
+ // ensure reads or writes cannot be performed if the socket has been shutdown |
+ // for reading or writing. |
+ ReceivePort receivePort = new ReceivePort(); |
+ Isolate echo = await Isolate.spawn(echoServer, receivePort.sendPort); |
+ |
+ Map response = await receivePort.first; |
+ SendPort sendPort = response[EchoServerTypes.ISOLATE_SEND_PORT]; |
+ int serverInternetPort = response[EchoServerTypes.SERVER_PORT]; |
+ List<int> data = createTestData(); |
+ |
+ receivePort = new ReceivePort(); |
+ echo.addErrorListener(receivePort.sendPort); |
+ |
+ Map limits = { |
+ EchoServerTypes.OFFSET_START: 0, |
+ EchoServerTypes.OFFSET_END: data.length, |
+ EchoServerTypes.ISOLATE_SEND_PORT: receivePort.sendPort, |
+ // Tell the server to shutdown after 3 sockets disconnect. |
+ EchoServerTypes.CONNECTION_COUNT: 3 |
+ }; |
+ sendPort.send(limits); |
+ |
+ try { |
+ var socket = RawSynchronousSocket.connectSync( |
+ LOOPBACK_IP_V4_STRING, serverInternetPort); |
+ |
+ // Close from both directions. Shouldn't be able to read/write to the |
+ // socket. |
+ socket.shutdown(SocketDirection.BOTH); |
+ Expect.throws( |
+ () => socket.writeFromSync(data), (e) => e is SocketException); |
+ Expect.throws( |
+ () => socket.readSync(data.length), (e) => e is SocketException); |
+ socket.closeSync(); |
+ |
+ // Close the socket for reading, do a write, and see if we can get any |
+ // response from the server (we shouldn't be able to). |
+ socket = RawSynchronousSocket.connectSync( |
+ LOOPBACK_IP_V4_STRING, serverInternetPort); |
+ socket.shutdown(SocketDirection.RECEIVE); |
+ socket.writeFromSync(data); |
+ // Throws exception when the socket is closed for RECEIVE. |
+ Expect.throws( |
+ () => socket.readSync(data.length), (e) => e is SocketException); |
+ Expect.isTrue(socket.available() == 0); |
+ socket.closeSync(); |
+ |
+ // Close the socket for writing and try to do a write. This should cause an |
+ // OSError to be throw as the pipe is closed for writing. |
+ socket = RawSynchronousSocket.connectSync( |
+ LOOPBACK_IP_V4_STRING, serverInternetPort); |
+ socket.shutdown(SocketDirection.SEND); |
+ Expect.throws( |
+ () => socket.writeFromSync(data), (e) => e is SocketException); |
+ socket.closeSync(); |
+ } catch (e, stack) { |
+ print("Echo test failed in client."); |
+ rethrow; |
+ } |
+ // Wait for the server to shutdown before finishing the test. |
+ var result = await receivePort.first; |
+ if (result != null) { |
+ throw "Echo test failed in server!\nError: ${result[0]}\nStack trace:" + |
+ " ${result[1]}"; |
+ } |
+ asyncEnd(); |
+} |
+ |
+Future testInvalidReadWriteOperations() { |
+ asyncStart(); |
+ RawServerSocket.bind(InternetAddress.LOOPBACK_IP_V4, 0).then((server) { |
+ server.listen((socket) {}); |
+ List<int> data = createTestData(); |
+ var socket = |
+ RawSynchronousSocket.connectSync(LOOPBACK_IP_V4_STRING, server.port); |
+ |
+ // Invalid writeFromSync invocations |
+ Expect.throws(() => socket.writeFromSync(data, data.length + 1), |
+ (e) => e is RangeError); |
+ Expect.throws(() => socket.writeFromSync(data, 0, data.length + 1), |
+ (e) => e is RangeError); |
+ Expect.throws( |
+ () => socket.writeFromSync(data, 1, 0), (e) => e is RangeError); |
+ Expect.throws( |
+ () => socket.writeFromSync(data, null), (e) => e is ArgumentError); |
+ |
+ // Invalid readIntoSync invocations |
+ List<int> buffer = new List<int>(10); |
+ Expect.throws(() => socket.readIntoSync(buffer, buffer.length + 1), |
+ (e) => e is RangeError); |
+ Expect.throws(() => socket.readIntoSync(buffer, 0, buffer.length + 1), |
+ (e) => e is RangeError); |
+ Expect.throws( |
+ () => socket.readIntoSync(buffer, 1, 0), (e) => e is RangeError); |
+ Expect.throws( |
+ () => socket.readIntoSync(buffer, null), (e) => e is ArgumentError); |
+ |
+ // Invalid readSync invocation |
+ Expect.throws(() => socket.readSync(-1), (e) => e is ArgumentError); |
+ |
+ server.close(); |
+ socket.closeSync(); |
+ asyncEnd(); |
+ }); |
+} |
+ |
+void testClosedError() { |
+ asyncStart(); |
+ RawServerSocket.bind(InternetAddress.LOOPBACK_IP_V4, 0).then((server) { |
+ server.listen((socket) { |
+ socket.close(); |
+ }); |
+ var socket = |
+ RawSynchronousSocket.connectSync(LOOPBACK_IP_V4_STRING, server.port); |
+ server.close(); |
+ socket.closeSync(); |
+ Expect.throws(() => socket.remotePort, (e) => e is SocketException); |
+ Expect.throws(() => socket.remoteAddress, (e) => e is SocketException); |
+ asyncEnd(); |
+ }); |
+} |
+ |
+main() async { |
+ asyncStart(); |
+ testArguments(); |
+ // testInvalidConnect(); Long timeout for bad lookups, so disable for bots. |
+ await testShutdown(); |
+ testSimpleConnect(); |
+ testServerListenAfterConnect(); |
+ await testSimpleReadWrite(); |
+ await testPartialRead(); |
+ await testPartialWrite(); |
+ testInvalidReadWriteOperations(); |
+ testClosedError(); |
+ asyncEnd(); |
+} |