From 8d99dd1534f1df553a9fb0ce08575733cf7b8aad Mon Sep 17 00:00:00 2001 From: Martin Pitt Date: Sat, 21 Dec 2024 09:22:40 +0100 Subject: [PATCH 1/6] Add umockdev_testbed_wait_script() Script replay happens in the background in a thread. This API waits for a script (or evemu events, which are translated to scripts) to finish, so that tests can synchronize them with other system events. Fixes #247 --- src/umockdev.vala | 34 ++++++++++++++++++++++ tests/test-umockdev.c | 65 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 99 insertions(+) diff --git a/src/umockdev.vala b/src/umockdev.vala index 588c703..43dedfe 100644 --- a/src/umockdev.vala +++ b/src/umockdev.vala @@ -1063,6 +1063,28 @@ public class Testbed: GLib.Object { return true; } + /** + * umockdev_testbed_wait_script: + * @self: A #UMockdevTestbed. + * @dev: Device path (/dev/...) to wait for + * @error: return location for a GError, or %NULL + * + * Wait for a previously loaded script or evemu record to finish. + * + * Returns: %TRUE on success, %FALSE if @dev is invalid and an error + * occurred. + */ + public bool wait_script (string dev) + throws FileError + { + unowned var runner = this.dev_script_runner.lookup (dev); + if (runner == null) + throw new FileError.NOENT (dev + " has no active script runner"); + runner.wait (); + this.dev_script_runner.remove (dev); + return true; + } + /** * umockdev_testbed_load_socket_script: * @self: A #UMockdevTestbed. @@ -1772,6 +1794,18 @@ private class ScriptRunner { this.thread.join (); } + public void wait () + { + if (!this.running) { + debug ("Script runner for %s already finished, nothing to wait for", this.device); + return; + } + + debug ("Waiting for script runner for %s to finish", this.device); + this.thread.join (); + assert (this.running == false); + } + private void* run () { char op; diff --git a/tests/test-umockdev.c b/tests/test-umockdev.c index 480ed78..4be1934 100644 --- a/tests/test-umockdev.c +++ b/tests/test-umockdev.c @@ -1747,6 +1747,11 @@ r 0 ^@^^^`^@a\n"; /* end of script */ ASSERT_EOF; + /* waiting is a no-op */ + success = umockdev_testbed_wait_script(fixture->testbed, "/dev/greeter", &error); + g_assert(success); + g_assert_no_error(error); + close(fd); } @@ -1830,6 +1835,64 @@ r 0 OK\n"; close(fd); } +static void +t_testbed_script_replay_wait(UMockdevTestbedFixture * fixture, UNUSED_DATA) +{ + gboolean success; + GError *error = NULL; + g_autofree char *tmppath = NULL; + int fd; + char buf[1024]; + + static const char* test_script = "r 100 Hello \n\ +r 100 world\n"; + + umockdev_testbed_add_from_string(fixture->testbed, + "P: /devices/greeter\nN: greeter\n" + "E: DEVNAME=/dev/greeter\nE: SUBSYSTEM=tty\nA: dev=4:64\n", &error); + g_assert_no_error(error); + + /* write script into temporary file */ + fd = g_file_open_tmp("test_script_simple.XXXXXX", &tmppath, &error); + g_assert_no_error(error); + g_assert_cmpint(write(fd, test_script, strlen(test_script)), >, 10); + close(fd); + + /* load it */ + success = umockdev_testbed_load_script(fixture->testbed, "/dev/greeter", tmppath, &error); + g_assert_no_error(error); + g_assert(success); + g_unlink (tmppath); + + /* wait for it; this writes the output into the pipe buffer, and theoretically may block + * take the risk for the unit test, it's small enough */ + success = umockdev_testbed_wait_script(fixture->testbed, "/dev/greeter", &error); + g_assert(success); + g_assert_no_error(error); + + /* start communication */ + fd = g_open("/dev/greeter", O_RDWR, 0); + g_assert_cmpint(fd, >=, 0); + + /* we get the full message in a single read */ + g_assert_cmpint(read(fd, buf, sizeof buf), ==, 11); + g_assert_cmpint(memcmp(buf, "Hello world", 11), ==, 0); + + close(fd); + + /* device has no script any more */ + success = umockdev_testbed_wait_script(fixture->testbed, "/dev/greeter", &error); + g_assert_false(success); + g_assert_error(error, G_FILE_ERROR, G_FILE_ERROR_NOENT); + g_clear_error(&error); + + /* invalid device */ + success = umockdev_testbed_wait_script(fixture->testbed, "/dev/invalid", &error); + g_assert_false(success); + g_assert_error(error, G_FILE_ERROR, G_FILE_ERROR_NOENT); + g_clear_error(&error); +} + static void t_testbed_script_replay_socket_stream(UMockdevTestbedFixture * fixture, UNUSED_DATA) { @@ -2389,6 +2452,8 @@ main(int argc, char **argv) t_testbed_script_replay_default_device, t_testbed_fixture_teardown); g_test_add("/umockdev-testbed/script_replay_override_default_device", UMockdevTestbedFixture, NULL, t_testbed_fixture_setup, t_testbed_script_replay_override_default_device, t_testbed_fixture_teardown); + g_test_add("/umockdev-testbed/script_replay_wait", UMockdevTestbedFixture, NULL, t_testbed_fixture_setup, + t_testbed_script_replay_wait, t_testbed_fixture_teardown); g_test_add("/umockdev-testbed/script_replay_evdev_event_framing", UMockdevTestbedFixture, NULL, t_testbed_fixture_setup, t_testbed_script_replay_evdev_event_framing, t_testbed_fixture_teardown); g_test_add("/umockdev-testbed/script_replay_socket_stream", UMockdevTestbedFixture, NULL, t_testbed_fixture_setup, From 098c9ca69f614a3e9c03eb69c3152a277e6aa517 Mon Sep 17 00:00:00 2001 From: Martin Pitt Date: Sat, 21 Dec 2024 09:37:42 +0100 Subject: [PATCH 2/6] Reorder ScriptRunner constructor args Start with the emulated device fd, it belongs together with the (purely descriptive) `device` argument. --- src/umockdev.vala | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/src/umockdev.vala b/src/umockdev.vala index 43dedfe..db15c1a 100644 --- a/src/umockdev.vala +++ b/src/umockdev.vala @@ -1059,7 +1059,7 @@ public class Testbed: GLib.Object { if (fd < 0) throw new FileError.INVAL (owned_dev + " is not a device suitable for scripts"); - this.dev_script_runner.insert (owned_dev, new ScriptRunner (owned_dev, recordfile, fd)); + this.dev_script_runner.insert (owned_dev, new ScriptRunner (fd, owned_dev, recordfile)); return true; } @@ -1765,15 +1765,24 @@ find_devnode (string devpath) private class ScriptRunner { - public ScriptRunner (string device, string script_file, int fd) throws FileError + /** + * ScriptRunner: + * @device_fd: File descriptor of the emulated device node + * @device: Name of the emulated device node, only for debug/error messages + * @script_file: Path to the script to load and run + * + * Create a new script runner for running @script. + */ + public ScriptRunner (int device_fd, string device, string script_file) throws FileError { + this.fd = device_fd; + this.device = device; + this.script = FileStream.open (script_file, "r"); if (this.script == null) throw new FileError.FAILED ("Cannot open script record file " + script_file); - this.device = device; this.script_file = script_file; - this.fd = fd; this.running = true; this.thread = new Thread (device, this.run); @@ -2008,11 +2017,11 @@ private class ScriptRunner { return d; } + private int fd; public string device { get; private set; } + private FileStream script; private string script_file; private Thread thread; - private FileStream script; - private int fd; private bool running; private uint fuzz = 0; } @@ -2136,7 +2145,7 @@ private class SocketServer { debug ("socket server thread: accepted request on server socket fd %i, path %s, script %s", s.fd, sock_path, script); string key = "%s%i".printf (sock_path, fd); - this.script_runners.insert (key, new ScriptRunner (key, script, fd)); + this.script_runners.insert (key, new ScriptRunner (fd, key, script)); } catch (GLib.Error e) { error ("socket server thread: cannot launch ScriptRunner: %s", e.message); } From b98dfdedeb74b950a7673d286673695055d3c945 Mon Sep 17 00:00:00 2001 From: Martin Pitt Date: Sat, 21 Dec 2024 11:54:48 +0100 Subject: [PATCH 3/6] Rewrite ScriptRunner to DataInputStream API This will make it possible to hand it scripts which are not in files. --- src/umockdev.vala | 118 ++++++++++++++++++++++++++++------------------ 1 file changed, 73 insertions(+), 45 deletions(-) diff --git a/src/umockdev.vala b/src/umockdev.vala index db15c1a..6d7f85d 100644 --- a/src/umockdev.vala +++ b/src/umockdev.vala @@ -1034,22 +1034,23 @@ public class Testbed: GLib.Object { public bool load_script (string? dev, string recordfile) throws GLib.Error, FileError, IOError, RegexError { + var script = new DataInputStream(File.new_for_path(recordfile).read()); + string? owned_dev = dev; if (owned_dev == null) { - var recording = new DataInputStream(File.new_for_path(recordfile).read()); // Ignore any leading comments - string line = recording.read_line(); + string line = script.read_line(); while (line != null && line.has_prefix("#")) - line = recording.read_line(); + line = script.read_line(); // Next must be our d 0 header if (line == null) - error("script recording %s has no non-comment content", recordfile); + error("script %s has no non-comment content", recordfile); MatchInfo header_matcher; if (!(new Regex("^d 0 (.*)(\n|$)")).match(line, 0, out header_matcher)) - error("null passed for device node, but recording %s has no d 0 header", recordfile); + error("null passed for device node, but script %s has no d 0 header", recordfile); owned_dev = header_matcher.fetch(1); } @@ -1059,7 +1060,7 @@ public class Testbed: GLib.Object { if (fd < 0) throw new FileError.INVAL (owned_dev + " is not a device suitable for scripts"); - this.dev_script_runner.insert (owned_dev, new ScriptRunner (fd, owned_dev, recordfile)); + this.dev_script_runner.insert (owned_dev, new ScriptRunner (fd, owned_dev, script, recordfile)); return true; } @@ -1769,19 +1770,16 @@ private class ScriptRunner { * ScriptRunner: * @device_fd: File descriptor of the emulated device node * @device: Name of the emulated device node, only for debug/error messages - * @script_file: Path to the script to load and run + * @script: Script to load and run + * @script_file: Description (e.g. file name) of @script, only for debug/error messages * * Create a new script runner for running @script. */ - public ScriptRunner (int device_fd, string device, string script_file) throws FileError + public ScriptRunner (int device_fd, string device, DataInputStream script, string script_file) throws FileError { this.fd = device_fd; this.device = device; - - this.script = FileStream.open (script_file, "r"); - if (this.script == null) - throw new FileError.FAILED ("Cannot open script record file " + script_file); - + this.script = script; this.script_file = script_file; this.running = true; @@ -1871,37 +1869,58 @@ private class ScriptRunner { private uint8[] next_line (out char op, out uint32 delta) { - // read operation code; skip empty lines and comments - int c; - for (;;) { - c = this.script.getc (); - if (c == FileStream.EOF) { - debug ("ScriptRunner[%s]: end of script %s, closing", this.device, this.script_file); - op = 'Q'; - delta = 0; - return {}; - } else if (c == '#') { - assert (this.script.read_line () != null); - } else if (c != '\n') { - op = (char) c; - break; + try { + // read operation code; skip empty lines and comments + for (;;) { + int c; + try { + c = this.script.read_byte (); + } catch (GLib.IOError e) { + if (e.code == GLib.IOError.FAILED) { + debug ("ScriptRunner[%s]: end of script %s, closing: %s", this.device, this.script_file, e.message); + op = 'Q'; + delta = 0; + return {}; + } + error ("ScriptRunner[%s]: failed to read script %s: %s", this.device, this.script_file, e.message); + } + if (c == '#') { + assert (this.script.read_line () != null); + } else if (c != '\n') { + op = (char) c; + break; + } } - } - var cur_pos = this.script.tell (); - if (this.script.getc () != ' ') - error ("Missing space after operation code in %s at position %li", this.script_file, cur_pos); + var cur_pos = this.script.tell (); + if (this.script.read_byte () != ' ') + error ("Missing space after operation code in %s at position %" + uint64.FORMAT, this.script_file, cur_pos); + + // read time delta + cur_pos = this.script.tell (); + string delta_str; + try { + size_t len; + delta_str = this.script.read_upto (" ", -1, out len, null); + } catch (GLib.IOError e) { + error ("No time value in %s at position %" + uint64.FORMAT + ": %s", this.script_file, cur_pos, e.message); + } + uint8 b = this.script.read_byte(); + assert(b == ' '); - // read time delta - cur_pos = this.script.tell (); - if (this.script.scanf ("%" + uint32.FORMAT + " ", out delta) != 1) - error ("Cannot parse time in %s at position %li", this.script_file, cur_pos); + int d; + if (!int.try_parse (delta_str, out d) || d < 0) + error ("Invalid time value '%s' in %s at position %" + uint64.FORMAT, delta_str, this.script_file, cur_pos); + delta = d; - // remainder of the line is the data - string? line = this.script.read_line (); - assert (line != null); + // remainder of the line is the data + string? line = this.script.read_line (); + assert (line != null); - return decode (line); + return decode (line); + } catch (GLib.IOError e) { + error ("ScriptRunner[%s]: failed to read script %s: %s", this.device, this.script_file, e.message); + } } private void op_write (uint8[] data, uint32 delta) @@ -1934,7 +1953,11 @@ private class ScriptRunner { if (ret <= 0) { debug ("ScriptRunner[%s]: got failure or EOF on read operation on expected block '%s', resetting", this.device, encode(data[len:data.length])); - this.script.seek (0, FileSeek.SET); + try { + this.script.seek (0, SeekType.SET); + } catch (GLib.Error e) { + error ("ScriptRunner[%s]: failed to reset script %s: %s", this.device, this.script_file, e.message); + } return; } @@ -1972,8 +1995,12 @@ private class ScriptRunner { private static uint8[] decode (string quoted) { + int i; + // skip over initial whitespace + for (i = 0; quoted.data[i] == ' ' && i < quoted.length; ++i); + uint8[] data = {}; - for (int i = 0; i < quoted.length; ++i) { + for (; i < quoted.length; ++i) { if (quoted.data[i] == '^') { assert (i + 1 < quoted.length); data += (quoted.data[i+1] == '`') ? '^' : (quoted.data[i+1] - 64); @@ -2019,7 +2046,7 @@ private class ScriptRunner { private int fd; public string device { get; private set; } - private FileStream script; + private DataInputStream script; private string script_file; private Thread thread; private bool running; @@ -2141,11 +2168,12 @@ private class SocketServer { string sock_path = null; try { sock_path = ((UnixSocketAddress) s.get_local_address()).path; - string script = this.socket_scriptfile.get (sock_path); + string scriptfile = this.socket_scriptfile.get (sock_path); debug ("socket server thread: accepted request on server socket fd %i, path %s, script %s", - s.fd, sock_path, script); + s.fd, sock_path, scriptfile); string key = "%s%i".printf (sock_path, fd); - this.script_runners.insert (key, new ScriptRunner (fd, key, script)); + var script = new DataInputStream(File.new_for_path(scriptfile).read()); + this.script_runners.insert (key, new ScriptRunner (fd, key, script, scriptfile)); } catch (GLib.Error e) { error ("socket server thread: cannot launch ScriptRunner: %s", e.message); } From 1243389e48008d3a5f3d4c8e300306937ff8d17b Mon Sep 17 00:00:00 2001 From: Martin Pitt Date: Sat, 21 Dec 2024 12:07:08 +0100 Subject: [PATCH 4/6] Add umockdev_testbed_load_script_from_string() This makes it more comfortable to deal with lots of small hand-crafted scripts instead of few big records. --- src/umockdev.vala | 37 +++++++++++++++++++++++++++++++++---- tests/test-umockdev.c | 39 +++++++++++++++++---------------------- 2 files changed, 50 insertions(+), 26 deletions(-) diff --git a/src/umockdev.vala b/src/umockdev.vala index 6d7f85d..ac15a9a 100644 --- a/src/umockdev.vala +++ b/src/umockdev.vala @@ -1034,8 +1034,37 @@ public class Testbed: GLib.Object { public bool load_script (string? dev, string recordfile) throws GLib.Error, FileError, IOError, RegexError { - var script = new DataInputStream(File.new_for_path(recordfile).read()); + return this.load_script_from_stream(dev, + new DataInputStream(File.new_for_path(recordfile).read()), + recordfile); + } + + /** + * umockdev_testbed_load_script_from_string: + * @self: A #UMockdevTestbed. + * @dev: Device path (/dev/...) for which to load the script record. + * %NULL is valid; in this case the script is associated with + * the device node it was recorded from. + * @script: script string + * @error: return location for a GError, or %NULL + * + * Load a script record file for a particular device into the testbed. + * script records can be created with umockdev-record --script. + * + * Returns: %TRUE on success, %FALSE if @recordfile is invalid and an error + * occurred. + */ + public bool load_script_from_string (string? dev, string script) + throws GLib.Error, IOError, RegexError + { + return this.load_script_from_stream(dev, + new DataInputStream(new MemoryInputStream.from_data(script.data)), + ""); + } + private bool load_script_from_stream (string? dev, DataInputStream script, string scriptname) + throws GLib.Error, IOError, RegexError + { string? owned_dev = dev; if (owned_dev == null) { @@ -1046,11 +1075,11 @@ public class Testbed: GLib.Object { // Next must be our d 0 header if (line == null) - error("script %s has no non-comment content", recordfile); + error("script %s has no non-comment content", scriptname); MatchInfo header_matcher; if (!(new Regex("^d 0 (.*)(\n|$)")).match(line, 0, out header_matcher)) - error("null passed for device node, but script %s has no d 0 header", recordfile); + error("null passed for device node, but script %s has no d 0 header", scriptname); owned_dev = header_matcher.fetch(1); } @@ -1060,7 +1089,7 @@ public class Testbed: GLib.Object { if (fd < 0) throw new FileError.INVAL (owned_dev + " is not a device suitable for scripts"); - this.dev_script_runner.insert (owned_dev, new ScriptRunner (fd, owned_dev, script, recordfile)); + this.dev_script_runner.insert (owned_dev, new ScriptRunner (fd, owned_dev, script, scriptname)); return true; } diff --git a/tests/test-umockdev.c b/tests/test-umockdev.c index 4be1934..946f9af 100644 --- a/tests/test-umockdev.c +++ b/tests/test-umockdev.c @@ -1840,8 +1840,6 @@ t_testbed_script_replay_wait(UMockdevTestbedFixture * fixture, UNUSED_DATA) { gboolean success; GError *error = NULL; - g_autofree char *tmppath = NULL; - int fd; char buf[1024]; static const char* test_script = "r 100 Hello \n\ @@ -1852,17 +1850,10 @@ r 100 world\n"; "E: DEVNAME=/dev/greeter\nE: SUBSYSTEM=tty\nA: dev=4:64\n", &error); g_assert_no_error(error); - /* write script into temporary file */ - fd = g_file_open_tmp("test_script_simple.XXXXXX", &tmppath, &error); - g_assert_no_error(error); - g_assert_cmpint(write(fd, test_script, strlen(test_script)), >, 10); - close(fd); - /* load it */ - success = umockdev_testbed_load_script(fixture->testbed, "/dev/greeter", tmppath, &error); + success = umockdev_testbed_load_script_from_string(fixture->testbed, "/dev/greeter", test_script, &error); g_assert_no_error(error); g_assert(success); - g_unlink (tmppath); /* wait for it; this writes the output into the pipe buffer, and theoretically may block * take the risk for the unit test, it's small enough */ @@ -1871,7 +1862,7 @@ r 100 world\n"; g_assert_no_error(error); /* start communication */ - fd = g_open("/dev/greeter", O_RDWR, 0); + int fd = g_open("/dev/greeter", O_RDWR, 0); g_assert_cmpint(fd, >=, 0); /* we get the full message in a single read */ @@ -1886,6 +1877,19 @@ r 100 world\n"; g_assert_error(error, G_FILE_ERROR, G_FILE_ERROR_NOENT); g_clear_error(&error); + /* can load a new script into the same device after waiting */ + success = umockdev_testbed_load_script_from_string(fixture->testbed, "/dev/greeter", "r 50 Again\n", &error); + g_assert_no_error(error); + g_assert(success); + success = umockdev_testbed_wait_script(fixture->testbed, "/dev/greeter", &error); + g_assert(success); + g_assert_no_error(error); + fd = g_open("/dev/greeter", O_RDWR, 0); + g_assert_cmpint(fd, >=, 0); + g_assert_cmpint(read(fd, buf, sizeof buf), ==, 5); + g_assert_cmpint(memcmp(buf, "Again", 5), ==, 0); + close(fd); + /* invalid device */ success = umockdev_testbed_wait_script(fixture->testbed, "/dev/invalid", &error); g_assert_false(success); @@ -1964,8 +1968,6 @@ t_testbed_script_replay_fuzz(UMockdevTestbedFixture * fixture, UNUSED_DATA) { gboolean success; GError *error = NULL; - g_autofree char *tmppath = NULL; - int fd; char buf[1024]; static const char* test_script = "f 20 -\n\ @@ -1978,20 +1980,13 @@ r 0 OK\n"; "E: DEVNAME=/dev/fuzzy\nE: SUBSYSTEM=tty\nA: dev=4:64\n", &error); g_assert_no_error(error); - /* write script into temporary file */ - fd = g_file_open_tmp("test_script_fuzzy.XXXXXX", &tmppath, &error); - g_assert_no_error(error); - g_assert_cmpint(write(fd, test_script, strlen(test_script)), >, 10); - close(fd); - /* load it */ - success = umockdev_testbed_load_script(fixture->testbed, "/dev/fuzzy", tmppath, &error); + success = umockdev_testbed_load_script_from_string(fixture->testbed, "/dev/fuzzy", test_script, &error); g_assert_no_error(error); g_assert(success); - g_unlink (tmppath); /* start communication */ - fd = g_open("/dev/fuzzy", O_RDWR | O_NONBLOCK, 0); + int fd = g_open("/dev/fuzzy", O_RDWR | O_NONBLOCK, 0); g_assert_cmpint(fd, >=, 0); errno = 0; From fd56a354552a822290c07b5d17ab6f4618461be6 Mon Sep 17 00:00:00 2001 From: Martin Pitt Date: Sat, 21 Dec 2024 12:12:20 +0100 Subject: [PATCH 5/6] implement load_evemu_events() with load_script_from_string() This avoids the temporary file. --- src/umockdev.vala | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/src/umockdev.vala b/src/umockdev.vala index ac15a9a..2dfda20 100644 --- a/src/umockdev.vala +++ b/src/umockdev.vala @@ -1184,8 +1184,7 @@ public class Testbed: GLib.Object { var default_dev_re = new Regex("^# device (.*)$"); var event_re = new Regex("^E: ([0-9]+)\\.([0-9]+) +([0-9a-fA-F]+) +([0-9a-fA-F]+) +(-?[0-9]+) *#?"); - string script_file; - int script_fd = FileUtils.open_tmp ("evemu.XXXXXX.script", out script_file); + string script = ""; int delay = 0; bool first = true; @@ -1218,12 +1217,9 @@ public class Testbed: GLib.Object { uint8[] ev_data = new uint8[sizeof(LinuxFixes.Input.Event)]; Posix.memcpy(ev_data, &ev, ev_data.length); - string script_line = "r " + delay.to_string() + " " + ScriptRunner.encode(ev_data) + "\n"; - assert (Posix.write(script_fd, script_line, script_line.length) == script_line.length); + script += "r " + delay.to_string() + " " + ScriptRunner.encode(ev_data) + "\n"; } - Posix.close (script_fd); - string? owned_dev = dev; if (owned_dev == null) { if (recorded_dev == null) @@ -1231,9 +1227,7 @@ public class Testbed: GLib.Object { owned_dev = recorded_dev; } - bool ret = load_script(owned_dev, script_file); - FileUtils.unlink(script_file); - return ret; + return load_script_from_string(owned_dev, script); } private static HashTable bus_lookup_table; From 84544e9f269a2f1112a9a04321337515b181b2dd Mon Sep 17 00:00:00 2001 From: Martin Pitt Date: Sat, 21 Dec 2024 12:29:51 +0100 Subject: [PATCH 6/6] Add umockdev_testbed_load_evemu_events_from_string() This makes it more comfortable to deal with lots of small hand-crafted evemu events. --- src/umockdev.vala | 24 +++++++++++++++++++----- tests/test-umockdev.c | 31 ++++++++++++++++++++----------- 2 files changed, 39 insertions(+), 16 deletions(-) diff --git a/src/umockdev.vala b/src/umockdev.vala index 2dfda20..bb3c315 100644 --- a/src/umockdev.vala +++ b/src/umockdev.vala @@ -1174,8 +1174,22 @@ public class Testbed: GLib.Object { public bool load_evemu_events (string? dev, string eventsfile) throws GLib.Error, FileError, IOError, RegexError { - File f_ev = File.new_for_path(eventsfile); - var s_ev = new DataInputStream(f_ev.read()); + return this.load_evemu_events_from_stream(dev, + new DataInputStream(File.new_for_path(eventsfile).read()), + eventsfile); + } + + public bool load_evemu_events_from_string (string? dev, string events) + throws GLib.Error, FileError, IOError, RegexError + { + return this.load_evemu_events_from_stream(dev, + new DataInputStream(new MemoryInputStream.from_data(events.data)), + ""); + } + + private bool load_evemu_events_from_stream (string? dev, DataInputStream events, string eventsname) + throws GLib.Error, IOError, RegexError + { string line; string? recorded_dev = null; size_t len; @@ -1188,7 +1202,7 @@ public class Testbed: GLib.Object { int delay = 0; bool first = true; - while ((line = s_ev.read_line(out len)) != null) { + while ((line = events.read_line(out len)) != null) { if (default_dev_re.match(line, 0, out match)) { recorded_dev = match.fetch(1); continue; @@ -1196,7 +1210,7 @@ public class Testbed: GLib.Object { if (!event_re.match(line, 0, out match)) { if (!line.has_prefix("#")) - warning("Ignoring invalid line in %s: %s", eventsfile, line); + warning("Ignoring invalid line in %s: %s", eventsname, line); continue; } time_t ev_sec = (time_t) uint64.parse(match.fetch(1)); @@ -1223,7 +1237,7 @@ public class Testbed: GLib.Object { string? owned_dev = dev; if (owned_dev == null) { if (recorded_dev == null) - error("null passed for device node, but recording %s has no '# device' header", eventsfile); + error("null passed for device node, but recording %s has no '# device' header", eventsname); owned_dev = recorded_dev; } diff --git a/tests/test-umockdev.c b/tests/test-umockdev.c index 946f9af..01f6de7 100644 --- a/tests/test-umockdev.c +++ b/tests/test-umockdev.c @@ -2022,8 +2022,6 @@ t_testbed_replay_evemu_events(UMockdevTestbedFixture * fixture, UNUSED_DATA) { gboolean success; GError *error = NULL; - int fd; - g_autofree char *tmppath = NULL; struct timeval tv_begin, tv_end; struct input_event ev; static const char* test_data = "E: 1234.500000 0000 0000 0\n" /* SYN */ @@ -2038,20 +2036,13 @@ t_testbed_replay_evemu_events(UMockdevTestbedFixture * fixture, UNUSED_DATA) "E: DEVNAME=/dev/input/event1\nE: SUBSYSTEM=input\n", &error); g_assert_no_error(error); - /* write evemu events file */ - fd = g_file_open_tmp("test_evemu.XXXXXX", &tmppath, &error); - g_assert_no_error(error); - g_assert_cmpint(write(fd, test_data, strlen(test_data)), ==, strlen(test_data)); - close(fd); - /* load it */ - success = umockdev_testbed_load_evemu_events(fixture->testbed, "/dev/input/event1", tmppath, &error); + success = umockdev_testbed_load_evemu_events_from_string(fixture->testbed, "/dev/input/event1", test_data, &error); g_assert_no_error(error); g_assert(success); - g_unlink (tmppath); /* start communication */ - fd = g_open("/dev/input/event1", O_RDONLY, 0); + int fd = g_open("/dev/input/event1", O_RDONLY, 0); g_assert_cmpint(fd, >=, 0); g_assert_cmpint(gettimeofday(&tv_begin, NULL), ==, 0); @@ -2099,6 +2090,24 @@ t_testbed_replay_evemu_events(UMockdevTestbedFixture * fixture, UNUSED_DATA) g_assert_cmpint(ev.value, ==, 1); assert_delta_t(&tv_begin, &tv_end, 550); + /* "flush" the script by waiting for it */ + success = umockdev_testbed_wait_script(fixture->testbed, "/dev/input/event1", &error); + g_assert(success); + g_assert_no_error(error); + + /* can load a new script now */ + success = umockdev_testbed_load_evemu_events_from_string(fixture->testbed, "/dev/input/event1", + "E: 1239.200000 0001 0123 1\n", &error); + g_assert_no_error(error); + g_assert(success); + + g_assert_cmpint(read(fd, &ev, sizeof(ev)), ==, sizeof(ev)); + g_assert_cmpint(ev.input_event_sec, ==, 1239); + g_assert_cmpint(ev.input_event_usec, ==, 200000); + g_assert_cmpint(ev.type, ==, 1); + g_assert_cmpint(ev.code, ==, 0x123); + g_assert_cmpint(ev.value, ==, 1); + close(fd); }