Hi

On Wed, Oct 16, 2024 at 2:28 PM Roman Penyaev <r.peni...@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.peni...@gmail.com>
> Cc: "Marc-André Lureau" <marcandre.lur...@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
>


Reply via email to