[geary/mjog/imap-connection-fixes: 90/110] Add a simple mock server for testing network code
- From: Michael Gratton <mjog src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [geary/mjog/imap-connection-fixes: 90/110] Add a simple mock server for testing network code
- Date: Sat, 21 Mar 2020 07:20:11 +0000 (UTC)
commit 1b010687353c298f2c9ce3082a984b1215b8798c
Author: Michael Gratton <mike vee net>
Date: Sun Dec 29 01:56:02 2019 +1030
Add a simple mock server for testing network code
test/meson.build | 1 +
test/test-server.vala | 222 ++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 223 insertions(+)
---
diff --git a/test/meson.build b/test/meson.build
index 38a3aae2..f410f914 100644
--- a/test/meson.build
+++ b/test/meson.build
@@ -3,6 +3,7 @@ subdir('data')
geary_test_lib_sources = [
'mock-object.vala',
'test-case.vala',
+ 'test-server.vala',
]
geary_test_engine_sources = [
diff --git a/test/test-server.vala b/test/test-server.vala
new file mode 100644
index 00000000..39e99e70
--- /dev/null
+++ b/test/test-server.vala
@@ -0,0 +1,222 @@
+/*
+ * Copyright 2019 Michael Gratton <mike vee net>
+ *
+ * This software is licensed under the GNU Lesser General Public License
+ * (version 2.1 or later). See the COPYING file in this distribution.
+ */
+
+/**
+ * A simple mock server for testing network connections.
+ *
+ * To use it, unit tests should construct an instance as a fixture in
+ * set up, specify a test script by adding lines and then check the
+ * result, before stopping the server in tear down.
+ */
+public class TestServer : GLib.Object {
+
+
+ /** Possible actions a script may take. */
+ public enum Action {
+ /**
+ * The implicit first action.
+ *
+ * This does not need to be specified as a script action, it
+ * will always be taken when a client connects.
+ */
+ CONNECTED,
+
+ /** Send a line to the client. */
+ SEND_LINE,
+
+ /** Receive a line from the client. */
+ RECEIVE_LINE,
+
+ /** Wait for the client to disconnect. */
+ WAIT_FOR_DISCONNECT,
+
+ /** Disconnect immediately. */
+ DISCONNECT;
+ }
+
+
+ /** A line of the server's script. */
+ public struct Line {
+
+ /** The action to take for this line. */
+ public Action action;
+
+ /**
+ * The value for the action.
+ *
+ * If sending, this string will be sent. If receiving, the
+ * expected line.
+ */
+ public string value;
+
+ }
+
+ /** The result of executing a script line. */
+ public struct Result {
+
+ /** The expected action. */
+ public Line line;
+
+ /** Was the expected action successful. */
+ public bool succeeded;
+
+ /** The actual string sent by a client when not as expected. */
+ public string? actual;
+
+ /** In case of an error being thrown, the error itself. */
+ public GLib.Error? error;
+
+ }
+
+
+ private GLib.DataStreamNewlineType line_ending;
+ private uint16 port;
+ private GLib.ThreadedSocketService service =
+ new GLib.ThreadedSocketService(10);
+ private GLib.Cancellable running = new GLib.Cancellable();
+ private Gee.List<Line?> script = new Gee.ArrayList<Line?>();
+ private GLib.AsyncQueue<Result?> completion_queue =
+ new GLib.AsyncQueue<Result?>();
+
+
+ public TestServer(GLib.DataStreamNewlineType line_ending = CR_LF)
+ throws GLib.Error {
+ this.line_ending = line_ending;
+ this.port = this.service.add_any_inet_port(null);
+ this.service.run.connect((conn) => {
+ handle_connection(conn);
+ return true;
+ });
+ this.service.start();
+ }
+
+ public GLib.SocketConnectable get_client_address() {
+ return new GLib.NetworkAddress("localhost", this.port);
+ }
+
+ public void add_script_line(Action action, string value) {
+ this.script.add({ action, value });
+ }
+
+ public Result wait_for_script(GLib.MainContext loop) {
+ Result? result = null;
+ while (result == null) {
+ loop.iteration(false);
+ result = this.completion_queue.try_pop();
+ }
+ return result;
+ }
+
+ public void stop() {
+ this.service.stop();
+ this.running.cancel();
+ }
+
+ private void handle_connection(GLib.SocketConnection connection) {
+ debug("Connected");
+ var input = new GLib.DataInputStream(
+ connection.input_stream
+ );
+ input.set_newline_type(this.line_ending);
+
+ var output = new GLib.DataOutputStream(
+ connection.output_stream
+ );
+
+ Line connected_line = { CONNECTED, "" };
+ Result result = { connected_line, true, null, null };
+ foreach (var line in this.script) {
+ result.line = line;
+ switch (line.action) {
+ case SEND_LINE:
+ debug("Sending: %s", line.value);
+ try {
+ output.put_string(line.value);
+ switch (this.line_ending) {
+ case CR:
+ output.put_byte('\r');
+ break;
+ case LF:
+ output.put_byte('\n');
+ break;
+ default:
+ output.put_byte('\r');
+ output.put_byte('\n');
+ break;
+ }
+ } catch (GLib.Error err) {
+ result.succeeded = false;
+ result.error = err;
+ }
+ break;
+
+ case RECEIVE_LINE:
+ debug("Waiting for: %s", line.value);
+ try {
+ size_t len;
+ string? received = input.read_line(out len, this.running);
+ if (received == null || received != line.value) {
+ result.succeeded = false;
+ result.actual = received;
+ }
+ } catch (GLib.Error err) {
+ result.succeeded = false;
+ result.error = err;
+ }
+ break;
+
+ case WAIT_FOR_DISCONNECT:
+ debug("Waiting for disconnect");
+ var socket = connection.get_socket();
+ try {
+ uint8 buffer[4096];
+ while (socket.receive_with_blocking(buffer, true) > 0) { }
+ } catch (GLib.Error err) {
+ result.succeeded = false;
+ result.error = err;
+ }
+ break;
+
+ case DISCONNECT:
+ debug("Disconnecting");
+ try {
+ connection.close(this.running);
+ } catch (GLib.Error err) {
+ result.succeeded = false;
+ result.error = err;
+ }
+ break;
+ }
+
+ if (!result.succeeded) {
+ break;
+ }
+ }
+ if (result.succeeded) {
+ debug("Done");
+ } else if (result.error != null) {
+ warning("Error: %s", result.error.message);
+ } else if (result.line.action == RECEIVE_LINE) {
+ warning("Received unexpected line: %s", result.actual ?? "(null)");
+ } else {
+ warning("Failed for unknown reason");
+ }
+
+ if (connection.is_connected()) {
+ try {
+ connection.close(this.running);
+ } catch (GLib.Error err) {
+ warning(
+ "Error closing test server connection: %s", err.message
+ );
+ }
+ }
+
+ this.completion_queue.push(result);
+ }
+
+}
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]