[geary/mjog/imap-connection-fixes: 10/34] Add a simple mock server for testing network code



commit 05fedbf9441d02fdba25fe4d86e612546f27a8a3
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]