From: Roman Penyaev <r.peniaev@gmail.com>
Cc: "Roman Penyaev" <r.peniaev@gmail.com>,
"Marc-André Lureau" <marcandre.lureau@redhat.com>,
qemu-devel@nongnu.org
Subject: [PATCH v2 4/5] tests/unit/test-char: add unit test for the `mux-be` multiplexer
Date: Wed, 9 Oct 2024 19:45:16 +0200 [thread overview]
Message-ID: <20241009174517.286935-5-r.peniaev@gmail.com> (raw)
In-Reply-To: <20241009174517.286935-1-r.peniaev@gmail.com>
The test is trivial: several backends, 1 `mux-be`, 1 frontend
do the buffer write and read. Pipe is used for EAGAIN verification.
Signed-off-by: Roman Penyaev <r.peniaev@gmail.com>
Cc: "Marc-André Lureau" <marcandre.lureau@redhat.com>
Cc: qemu-devel@nongnu.org
---
tests/unit/test-char.c | 217 ++++++++++++++++++++++++++++++++++++++++-
1 file changed, 215 insertions(+), 2 deletions(-)
diff --git a/tests/unit/test-char.c b/tests/unit/test-char.c
index f273ce522612..554129c3b1a9 100644
--- a/tests/unit/test-char.c
+++ b/tests/unit/test-char.c
@@ -177,7 +177,7 @@ static void char_ringbuf_test(void)
qemu_opts_del(opts);
}
-static void char_mux_test(void)
+static void char_mux_fe_test(void)
{
QemuOpts *opts;
Chardev *chr, *base;
@@ -337,6 +337,218 @@ static void char_mux_test(void)
qemu_chr_fe_deinit(&chr_be2, true);
}
+static void char_mux_be_test(void)
+{
+ QemuOpts *opts;
+ Chardev *mux_be, *chr1, *chr2, *base;
+ char *data;
+ FeHandler h = { 0, false, 0, false, };
+ CharBackend chr_be;
+ int ret;
+
+#define RB_SIZE 128
+
+ /* Create mux-be */
+ opts = qemu_opts_create(qemu_find_opts("chardev"), "mux0",
+ 1, &error_abort);
+ qemu_opt_set(opts, "backend", "mux-be", &error_abort);
+ mux_be = qemu_chr_new_from_opts(opts, NULL, &error_abort);
+ g_assert_nonnull(mux_be);
+ qemu_opts_del(opts);
+
+ /* Create first chardev */
+ opts = qemu_opts_create(qemu_find_opts("chardev"), "chr1",
+ 1, &error_abort);
+ qemu_opt_set(opts, "backend", "ringbuf", &error_abort);
+ qemu_opt_set(opts, "size", stringify(RB_SIZE), &error_abort);
+ qemu_opt_set(opts, "mux-be-id", "mux0", &error_abort);
+ chr1 = qemu_chr_new_from_opts(opts, NULL, &error_abort);
+ g_assert_nonnull(chr1);
+ qemu_opts_del(opts);
+
+ /* Create second chardev */
+ opts = qemu_opts_create(qemu_find_opts("chardev"), "chr2",
+ 1, &error_abort);
+ qemu_opt_set(opts, "backend", "ringbuf", &error_abort);
+ qemu_opt_set(opts, "size", stringify(RB_SIZE), &error_abort);
+ qemu_opt_set(opts, "mux-be-id", "mux0", &error_abort);
+ chr2 = qemu_chr_new_from_opts(opts, NULL, &error_abort);
+ g_assert_nonnull(chr2);
+ qemu_opts_del(opts);
+
+ /* Attach mux-be to a frontend */
+ qemu_chr_fe_init(&chr_be, mux_be, &error_abort);
+ qemu_chr_fe_set_handlers(&chr_be,
+ fe_can_read,
+ fe_read,
+ fe_event,
+ NULL,
+ &h,
+ NULL, true);
+
+ /* Write to backend, chr1 */
+ base = qemu_chr_find("chr1");
+ g_assert_cmpint(qemu_chr_be_can_write(base), !=, 0);
+
+ qemu_chr_be_write(base, (void *)"hello", 6);
+ g_assert_cmpint(h.read_count, ==, 6);
+ g_assert_cmpstr(h.read_buf, ==, "hello");
+ h.read_count = 0;
+
+ /* Write to backend, chr2 */
+ base = qemu_chr_find("chr2");
+ g_assert_cmpint(qemu_chr_be_can_write(base), !=, 0);
+
+ qemu_chr_be_write(base, (void *)"olleh", 6);
+ g_assert_cmpint(h.read_count, ==, 6);
+ g_assert_cmpstr(h.read_buf, ==, "olleh");
+ h.read_count = 0;
+
+ /* Write to frontend, chr_be */
+ ret = qemu_chr_fe_write(&chr_be, (void *)"heyhey", 6);
+ g_assert_cmpint(ret, ==, 6);
+
+ data = qmp_ringbuf_read("chr1", RB_SIZE, false, 0, &error_abort);
+ g_assert_cmpint(strlen(data), ==, 6);
+ g_assert_cmpstr(data, ==, "heyhey");
+ g_free(data);
+
+ data = qmp_ringbuf_read("chr2", RB_SIZE, false, 0, &error_abort);
+ g_assert_cmpint(strlen(data), ==, 6);
+ g_assert_cmpstr(data, ==, "heyhey");
+ g_free(data);
+
+
+#ifndef _WIN32
+ /*
+ * Create third chardev to simulate EAGAIN and watcher.
+ * Mainly copied from char_pipe_test().
+ */
+ {
+ gchar *tmp_path = g_dir_make_tmp("qemu-test-char.XXXXXX", NULL);
+ gchar *in, *out, *pipe = g_build_filename(tmp_path, "pipe", NULL);
+ Chardev *chr3;
+ int fd, len;
+ char buf[128];
+
+ in = g_strdup_printf("%s.in", pipe);
+ if (mkfifo(in, 0600) < 0) {
+ abort();
+ }
+ out = g_strdup_printf("%s.out", pipe);
+ if (mkfifo(out, 0600) < 0) {
+ abort();
+ }
+
+ opts = qemu_opts_create(qemu_find_opts("chardev"), "chr3",
+ 1, &error_abort);
+ qemu_opt_set(opts, "backend", "pipe", &error_abort);
+ qemu_opt_set(opts, "path", pipe, &error_abort);
+ qemu_opt_set(opts, "mux-be-id", "mux0", &error_abort);
+ chr3 = qemu_chr_new_from_opts(opts, NULL, &error_abort);
+ g_assert_nonnull(chr3);
+
+ /* Write to frontend, chr_be */
+ ret = qemu_chr_fe_write(&chr_be, (void *)"thisis", 6);
+ g_assert_cmpint(ret, ==, 6);
+
+ data = qmp_ringbuf_read("chr1", RB_SIZE, false, 0, &error_abort);
+ g_assert_cmpint(strlen(data), ==, 6);
+ g_assert_cmpstr(data, ==, "thisis");
+ g_free(data);
+
+ data = qmp_ringbuf_read("chr2", RB_SIZE, false, 0, &error_abort);
+ g_assert_cmpint(strlen(data), ==, 6);
+ g_assert_cmpstr(data, ==, "thisis");
+ g_free(data);
+
+ fd = open(out, O_RDWR);
+ ret = read(fd, buf, sizeof(buf));
+ g_assert_cmpint(ret, ==, 6);
+ buf[ret] = 0;
+ g_assert_cmpstr(buf, ==, "thisis");
+ close(fd);
+
+ /* Add watch. 0 indicates no watches if nothing to wait for */
+ ret = qemu_chr_fe_add_watch(&chr_be, G_IO_OUT | G_IO_HUP,
+ NULL, NULL);
+ g_assert_cmpint(ret, ==, 0);
+
+ /*
+ * Write to frontend, chr_be, until EAGAIN. Make sure length is
+ * power of two to fit nicely the whole pipe buffer.
+ */
+ len = 0;
+ while ((ret = qemu_chr_fe_write(&chr_be, (void *)"thisisit", 8))
+ != -1) {
+ len += ret;
+ }
+ g_assert_cmpint(errno, ==, EAGAIN);
+
+ /* Further all writes should cause EAGAIN */
+ ret = qemu_chr_fe_write(&chr_be, (void *)"b", 1);
+ g_assert_cmpint(ret, ==, -1);
+ g_assert_cmpint(errno, ==, EAGAIN);
+
+ /*
+ * Add watch. Non 0 indicates we have a blocked chardev, which
+ * can wakes us up when write is possible.
+ */
+ ret = qemu_chr_fe_add_watch(&chr_be, G_IO_OUT | G_IO_HUP,
+ NULL, NULL);
+ g_assert_cmpint(ret, !=, 0);
+
+ /* Drain pipe and ring buffers */
+ fd = open(out, O_RDWR);
+ while ((ret = read(fd, buf, MIN(sizeof(buf), len))) != -1 && len > 0) {
+ len -= ret;
+ }
+ close(fd);
+
+ data = qmp_ringbuf_read("chr1", RB_SIZE, false, 0, &error_abort);
+ g_assert_cmpint(strlen(data), ==, 128);
+ g_free(data);
+
+ data = qmp_ringbuf_read("chr2", RB_SIZE, false, 0, &error_abort);
+ g_assert_cmpint(strlen(data), ==, 128);
+ g_free(data);
+
+ /*
+ * Now we are good to go, first repeat "lost" sequence, which
+ * was already consumed and drained by the ring buffers, but
+ * pipe have not recieved that yet.
+ */
+ ret = qemu_chr_fe_write(&chr_be, (void *)"thisisit", 8);
+ g_assert_cmpint(ret, ==, 8);
+
+ ret = qemu_chr_fe_write(&chr_be, (void *)"streamisrestored", 16);
+ g_assert_cmpint(ret, ==, 16);
+
+ data = qmp_ringbuf_read("chr1", RB_SIZE, false, 0, &error_abort);
+ g_assert_cmpint(strlen(data), ==, 16);
+ /* Only last 16 bytes, see big comment above */
+ g_assert_cmpstr(data, ==, "streamisrestored");
+ g_free(data);
+
+ data = qmp_ringbuf_read("chr2", RB_SIZE, false, 0, &error_abort);
+ g_assert_cmpint(strlen(data), ==, 16);
+ /* Only last 16 bytes, see big comment above */
+ g_assert_cmpstr(data, ==, "streamisrestored");
+ g_free(data);
+
+ fd = open(out, O_RDWR);
+ ret = read(fd, buf, sizeof(buf));
+ g_assert_cmpint(ret, ==, 24);
+ buf[ret] = 0;
+ /* Both 8 and 16 bytes */
+ g_assert_cmpstr(buf, ==, "thisisitstreamisrestored");
+ close(fd);
+ }
+#endif
+
+ /* Finalize */
+ qemu_chr_fe_deinit(&chr_be, false);
+}
static void websock_server_read(void *opaque, const uint8_t *buf, int size)
{
@@ -1484,7 +1696,8 @@ int main(int argc, char **argv)
g_test_add_func("/char/null", char_null_test);
g_test_add_func("/char/invalid", char_invalid_test);
g_test_add_func("/char/ringbuf", char_ringbuf_test);
- g_test_add_func("/char/mux", char_mux_test);
+ g_test_add_func("/char/mux", char_mux_fe_test);
+ g_test_add_func("/char/mux-be", char_mux_be_test);
#ifdef _WIN32
g_test_add_func("/char/console/subprocess", char_console_test_subprocess);
g_test_add_func("/char/console", char_console_test);
--
2.43.0
next prev parent reply other threads:[~2024-10-09 17:48 UTC|newest]
Thread overview: 8+ messages / expand[flat|nested] mbox.gz Atom feed top
2024-10-09 17:45 [PATCH v2 0/5] chardev: implement backend chardev multiplexing Roman Penyaev
2024-10-09 17:45 ` [PATCH v2 1/5] chardev/char: introduce `mux-be-id=ID` option and _MUX_BE type Roman Penyaev
2024-10-09 17:45 ` [PATCH v2 2/5] chardev/char: rename `mux_cnt` to `fe_cnt` for the `MuxChardev` Roman Penyaev
2024-10-09 17:45 ` [PATCH v2 3/5] chardev/char-mux: implement backend chardev multiplexing Roman Penyaev
2024-10-09 17:45 ` Roman Penyaev [this message]
2024-10-09 17:45 ` [PATCH v2 5/5] qemu-options.hx: describe multiplexing of several backend devices Roman Penyaev
2024-10-09 18:23 ` [PATCH v2 0/5] chardev: implement backend chardev multiplexing Marc-André Lureau
2024-10-10 8:34 ` Roman Penyaev
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20241009174517.286935-5-r.peniaev@gmail.com \
--to=r.peniaev@gmail.com \
--cc=marcandre.lureau@redhat.com \
--cc=qemu-devel@nongnu.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.