| 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..67b3e9ed7122b7e29eeb54adcf6108ad51a5766a
|
| --- /dev/null
|
| +++ b/tests/standalone/io/raw_synchronous_socket_test.dart
|
| @@ -0,0 +1,488 @@
|
| +// 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));
|
| +}
|
| +
|
| +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();
|
| + await testShutdown();
|
| + testSimpleConnect();
|
| + testServerListenAfterConnect();
|
| + await testSimpleReadWrite();
|
| + await testPartialRead();
|
| + await testPartialWrite();
|
| + testInvalidReadWriteOperations();
|
| + testClosedError();
|
| + asyncEnd();
|
| +}
|
|
|