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 | 306 ++++++++++++++++++++++++++++++++++++++++-
1 file changed, 304 insertions(+), 2 deletions(-)
diff --git a/tests/unit/test-char.c b/tests/unit/test-char.c
index a1c6bb874c8e..3eb0692b199f 100644
--- a/tests/unit/test-char.c
+++ b/tests/unit/test-char.c
@@ -178,7 +178,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;
@@ -359,6 +359,307 @@ static void char_mux_test(void)
qmp_chardev_remove("mux-label", &error_abort);
}
+static void char_mux_be_test(void)
+{
+ QemuOpts *opts;
+ Chardev *mux_be, *chr1, *chr2, *base;
+ char *data;
+ FeHandler h = { 0, false, 0, false, };
+ Error *error = NULL;
+ CharBackend chr_be;
+ int ret, i;
+
+#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);
+
+ /* Check maximum allowed backends */
+ for (i = 0; true; i++) {
+ char name[8];
+
+ snprintf(name, sizeof(name), "chr%d", i);
+ opts = qemu_opts_create(qemu_find_opts("chardev"), name,
+ 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);
+ base = qemu_chr_new_from_opts(opts, NULL, &error);
+ if (error) {
+ const char *err_fmt =
+ "too many uses of multiplexed chardev 'mux0' (maximum is %u)";
+ unsigned n;
+
+ ret = sscanf(error_get_pretty(error), err_fmt, &n);
+ error_free(error);
+ error = NULL;
+ g_assert_cmpint(ret, ==, 1);
+ g_assert_cmpint(i, ==, n);
+ break;
+ }
+ g_assert_nonnull(base);
+ qemu_opts_del(opts);
+ }
+ /* Finalize mux0 */
+ qmp_chardev_remove("mux0", &error_abort);
+
+ /* Finalize all backends */
+ while (i--) {
+ char name[8];
+ snprintf(name, sizeof(name), "chr%d", i);
+ qmp_chardev_remove(name, &error_abort);
+ }
+
+ /* 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 chardev which fails */
+ 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);
+ qemu_opt_set(opts, "mux", "on", &error_abort);
+ chr1 = qemu_chr_new_from_opts(opts, NULL, &error);
+ g_assert_cmpstr(error_get_pretty(error), ==, "chardev: mux and mux-be "
+ "can't be used for the same device");
+ error_free(error);
+ error = NULL;
+ 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);
+
+ /* Fails second time */
+ qemu_chr_fe_init(&chr_be, mux_be, &error);
+ g_assert_cmpstr(error_get_pretty(error), ==, "multiplexed chardev 'mux0' "
+ "is already used for multiplexing");
+ error_free(error);
+ error = NULL;
+
+ /* 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);
+ g_source_remove(ret);
+
+ /* 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
+
+ /* Can't be removed, depends on mux0 */
+ qmp_chardev_remove("chr1", &error);
+ g_assert_cmpstr(error_get_pretty(error), ==, "Chardev 'chr1' is busy");
+ error_free(error);
+ error = NULL;
+
+ /* Can't be removed, depends on frontend chr_be */
+ qmp_chardev_remove("mux0", &error);
+ g_assert_cmpstr(error_get_pretty(error), ==, "Chardev 'mux0' is busy");
+ error_free(error);
+ error = NULL;
+
+ /* Finalize frontend */
+ qemu_chr_fe_deinit(&chr_be, false);
+
+ /* Finalize mux0 */
+ qmp_chardev_remove("mux0", &error_abort);
+
+ /* Finalize backend chardevs */
+ qmp_chardev_remove("chr1", &error_abort);
+ qmp_chardev_remove("chr2", &error_abort);
+#ifndef _WIN32
+ qmp_chardev_remove("chr3", &error_abort);
+#endif
+}
static void websock_server_read(void *opaque, const uint8_t *buf, int size)
{
@@ -1506,7 +1807,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.34.1
Hi On Wed, Oct 16, 2024 at 2:28 PM Roman Penyaev <r.peniaev@gmail.com> wrote: > > 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 | 306 ++++++++++++++++++++++++++++++++++++++++- > 1 file changed, 304 insertions(+), 2 deletions(-) please fix the few leaks (found with --enable-asan) > > diff --git a/tests/unit/test-char.c b/tests/unit/test-char.c > index a1c6bb874c8e..3eb0692b199f 100644 > --- a/tests/unit/test-char.c > +++ b/tests/unit/test-char.c > @@ -178,7 +178,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; > @@ -359,6 +359,307 @@ static void char_mux_test(void) > qmp_chardev_remove("mux-label", &error_abort); > } > > +static void char_mux_be_test(void) > +{ > + QemuOpts *opts; > + Chardev *mux_be, *chr1, *chr2, *base; > + char *data; > + FeHandler h = { 0, false, 0, false, }; > + Error *error = NULL; > + CharBackend chr_be; > + int ret, i; > + > +#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); > + > + /* Check maximum allowed backends */ > + for (i = 0; true; i++) { > + char name[8]; > + > + snprintf(name, sizeof(name), "chr%d", i); > + opts = qemu_opts_create(qemu_find_opts("chardev"), name, > + 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); > + base = qemu_chr_new_from_opts(opts, NULL, &error); > + if (error) { > + const char *err_fmt = > + "too many uses of multiplexed chardev 'mux0' (maximum is %u)"; > + unsigned n; > + > + ret = sscanf(error_get_pretty(error), err_fmt, &n); > + error_free(error); > + error = NULL; > + g_assert_cmpint(ret, ==, 1); > + g_assert_cmpint(i, ==, n); > + break; > + } > + g_assert_nonnull(base); > + qemu_opts_del(opts); > + } > + /* Finalize mux0 */ > + qmp_chardev_remove("mux0", &error_abort); > + > + /* Finalize all backends */ > + while (i--) { > + char name[8]; > + snprintf(name, sizeof(name), "chr%d", i); > + qmp_chardev_remove(name, &error_abort); > + } > + > + /* 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 chardev which fails */ > + 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); > + qemu_opt_set(opts, "mux", "on", &error_abort); > + chr1 = qemu_chr_new_from_opts(opts, NULL, &error); > + g_assert_cmpstr(error_get_pretty(error), ==, "chardev: mux and mux-be " > + "can't be used for the same device"); > + error_free(error); > + error = NULL; > + 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); > + > + /* Fails second time */ > + qemu_chr_fe_init(&chr_be, mux_be, &error); > + g_assert_cmpstr(error_get_pretty(error), ==, "multiplexed chardev 'mux0' " > + "is already used for multiplexing"); > + error_free(error); > + error = NULL; > + > + /* 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); > + g_source_remove(ret); > + > + /* 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 > + > + /* Can't be removed, depends on mux0 */ > + qmp_chardev_remove("chr1", &error); > + g_assert_cmpstr(error_get_pretty(error), ==, "Chardev 'chr1' is busy"); > + error_free(error); > + error = NULL; > + > + /* Can't be removed, depends on frontend chr_be */ > + qmp_chardev_remove("mux0", &error); > + g_assert_cmpstr(error_get_pretty(error), ==, "Chardev 'mux0' is busy"); > + error_free(error); > + error = NULL; > + > + /* Finalize frontend */ > + qemu_chr_fe_deinit(&chr_be, false); > + > + /* Finalize mux0 */ > + qmp_chardev_remove("mux0", &error_abort); > + > + /* Finalize backend chardevs */ > + qmp_chardev_remove("chr1", &error_abort); > + qmp_chardev_remove("chr2", &error_abort); > +#ifndef _WIN32 > + qmp_chardev_remove("chr3", &error_abort); > +#endif > +} > > static void websock_server_read(void *opaque, const uint8_t *buf, int size) > { > @@ -1506,7 +1807,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.34.1 >
Hi Marc-André, On Wed, Oct 16, 2024 at 1:36 PM Marc-André Lureau <marcandre.lureau@redhat.com> wrote: > > Hi > > On Wed, Oct 16, 2024 at 2:28 PM Roman Penyaev <r.peniaev@gmail.com> wrote: > > > > 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 | 306 ++++++++++++++++++++++++++++++++++++++++- > > 1 file changed, 304 insertions(+), 2 deletions(-) > > please fix the few leaks (found with --enable-asan) Did not know about this option. Should be easy, thanks. -- Roman
© 2016 - 2024 Red Hat, Inc.