mbox series

[v2,00/11] 9pfs: add tests using local fs driver

Message ID cover.1601639563.git.qemu_oss@crudebyte.com
Headers show
Series 9pfs: add tests using local fs driver | expand

Message

Christian Schoenebeck Oct. 2, 2020, 11:52 a.m. UTC
The currently existing 9pfs test cases are all solely using the 9pfs 'synth'
fileystem driver, which is a very simple and purely simulated (in RAM only)
filesystem. There are issues though where the 'synth' fs driver is not
sufficient. For example the following two bugs need test cases running the
9pfs 'local' fs driver:

https://bugs.launchpad.net/qemu/+bug/1336794
https://bugs.launchpad.net/qemu/+bug/1877384

This patch set for that reason introduces 9pfs test cases using the 9pfs
'local' filesystem driver along to the already existing tests on 'synth'.
It consists of 3 parts:

1. libqos patches 1 and 2 remove a limitation of the qtest/libqos subsystem:
   support for more than one device using the same (official) QEMU device
   name.

2. Patches 3 to 6 enhance debugging issues with the qtest framework.

3. Patches 7 to 11 actually introduce 9pfs 'local' test cases using the qtest
   framework. These 'local' tests are adding a test directory 'qtest-9p-local'
   inside the current working directory (using get_current_dir()), which is
   typically the build directory, before running the tests. That test
   directory is automatically recreated next time the test suite is run again,
   to ensure the 9pfs 'local' tests always run consistently on a clean test
   directory. The test directory is used by the 'local' driver as root of its
   export path. So it will add/read/write/delete real files and directories
   inside that test directory.

v1->v2:

  * The libqos debugging features are now turned on by command line argument
    '--verbose' instead of using environment variables (patches 4, 5, 6).

  * The new 9pfs 'local' tests no longer depend on patches 1 and 2 by no
    longer using a libqos multi-device approach, but rather modifying the
    final QEMU command line for each test instead; see discussion of v1
    for reason (patches 7 to 11).

  * Use GCC_FMT_ATTR on helper function strpr() (patch 8).

  * Updated commit log comments.

Christian Schoenebeck (11):
  libqos/qgraph: add qemu_name to QOSGraphNode
  libqos/qgraph: add qos_node_create_driver_named()
  libqos/qgraph: add qos_dump_graph()
  tests/qtest/qos-test: dump qos graph if verbose
  tests/qtest/qos-test: dump environment variables if verbose
  tests/qtest/qos-test: dump QEMU command if verbose
  tests/9pfs: change qtest name prefix to synth
  tests/9pfs: introduce local tests
  tests/9pfs: wipe local 9pfs test directory
  tests/9pfs: add virtio_9p_test_path()
  tests/9pfs: add local Tmkdir test

 tests/qtest/libqos/qgraph.c          | 108 ++++++++++++++-
 tests/qtest/libqos/qgraph.h          |  36 +++++
 tests/qtest/libqos/qgraph_internal.h |   1 +
 tests/qtest/libqos/virtio-9p.c       | 119 ++++++++++++++++
 tests/qtest/libqos/virtio-9p.h       |  10 ++
 tests/qtest/qos-test.c               |  15 +-
 tests/qtest/virtio-9p-test.c         | 197 ++++++++++++++++++++++++---
 7 files changed, 461 insertions(+), 25 deletions(-)

Comments

Daniel P. Berrangé Oct. 2, 2020, 2:23 p.m. UTC | #1
On Fri, Oct 02, 2020 at 04:09:53PM +0200, Christian Schoenebeck wrote:
> On Freitag, 2. Oktober 2020 15:44:40 CEST Daniel P. Berrangé wrote:
> > On Fri, Oct 02, 2020 at 03:41:07PM +0200, Christian Schoenebeck wrote:
> > > On Freitag, 2. Oktober 2020 14:56:14 CEST Daniel P. Berrangé wrote:
> > > > On Fri, Oct 02, 2020 at 01:51:54PM +0200, Christian Schoenebeck wrote:
> > > > > This test case uses the 9pfs 'local' driver to create a directory
> > > > > and then checks if the expected directory was actually created
> > > > > (as real directory) on host side.
> > > > > 
> > > > > Signed-off-by: Christian Schoenebeck <qemu_oss@crudebyte.com>
> > > > > ---
> > > > > 
> > > > >  tests/qtest/virtio-9p-test.c | 139
> > > > >  +++++++++++++++++++++++++++++++++++
> > > > >  1 file changed, 139 insertions(+)
> > > > > 
> > > > > diff --git a/tests/qtest/virtio-9p-test.c
> > > > > b/tests/qtest/virtio-9p-test.c
> > > > > index af7e169d3a..93161a4b35 100644
> > > > > --- a/tests/qtest/virtio-9p-test.c
> > > > > +++ b/tests/qtest/virtio-9p-test.c
> > > > > @@ -18,6 +18,62 @@
> > > > > 
> > > > >  #define QVIRTIO_9P_TIMEOUT_US (10 * 1000 * 1000)
> > > > >  static QGuestAllocator *alloc;
> > > > > 
> > > > > +/*
> > > > > + * Used to auto generate new fids. Start with arbitrary high value to
> > > > > avoid + * collision with hard coded fids in basic test code.
> > > > > + */
> > > > > +static uint32_t fid_generator = 1000;
> > > > > +
> > > > > +static uint32_t genfid(void)
> > > > > +{
> > > > > +    return fid_generator++;
> > > > > +}
> > > > > +
> > > > > +/**
> > > > > + * Splits the @a in string by @a delim into individual (non empty)
> > > > > strings
> > > > > + * and outputs them to @a out. The output array @a out is NULL
> > > > > terminated.
> > > > > + *
> > > > > + * Output array @a out must be freed by calling split_free().
> > > > > + *
> > > > > + * @returns number of individual elements in output array @a out
> > > > > (without
> > > > > the + *          final NULL terminating element)
> > > > > + */
> > > > > +static int split(const char *in, const char *delim, char ***out)
> > > > > +{
> > > > > +    int n = 0, i = 0;
> > > > > +    char *tmp, *p;
> > > > > +
> > > > > +    tmp = g_strdup(in);
> > > > > +    for (p = strtok(tmp, delim); p != NULL; p = strtok(NULL, delim))
> > > > > {
> > > > > +        if (strlen(p) > 0) {
> > > > > +            ++n;
> > > > > +        }
> > > > > +    }
> > > > > +    g_free(tmp);
> > > > > +
> > > > > +    *out = g_malloc0(n * sizeof(char *) + 1); /* last element NULL
> > > > > delimiter */
> > > > 
> > > > Surely this should be  (n + 1) * sizeof(char *), because the last
> > > > element still needs to be large enough to hold a pointer, not a
> > > > single extra byte.
> > > 
> > > Right, good catch!
> > > 
> > > > > +
> > > > > +    tmp = g_strdup(in);
> > > > > +    for (p = strtok(tmp, delim); p != NULL; p = strtok(NULL, delim))
> > > > > {
> > > > > +        if (strlen(p) > 0) {
> > > > > +            (*out)[i++] = g_strdup(p);
> > > > > +        }
> > > > > +    }
> > > > > +    g_free(tmp);
> > > > > +
> > > > > +    return n;
> > > > > +}
> > > > 
> > > > This seems to largely re-invent g_strsplit
> > > > 
> > > > https://developer.gnome.org/glib/2.62/glib-String-Utility-Functions.html
> > > > #g-s trsplit
> > > 
> > > Yes, except that g_strsplit() outputs empty array elements as well. That's
> > > not desired/working for this patch.
> > 
> > Either make the caller ignore/skip over empty elements, or make
> 
> Not an option, since it would create too much test code overhead. I really 
> need [const int elements] and [array without empty elements].
> 
> > this method call g_strsplit and then filter out the empty elements.
> 
> Mmm, so you're suggesting to allocate a new array, copy elements from 
> g_strsplit() array to the new array, and eventually pass that manually 
> allocated array to g_strfreev()? Wouldn't that be a bit unsafe regarding 
> potential future changes in how glib allocates/structures those string arrays?

No need to realloate a new array - just shuffle down the elements in
the array you get back from g_strsplit to discard the empty ones. Sure
the array will end up with a series of NULLs at the end but that is
harmless.

both g_strsplit/g_strfreev work in terms of a normal allocated "char **"
array, so there's no risk of changes to way memory is allocated.

Regards,
Daniel
Christian Schoenebeck Oct. 2, 2020, 2:26 p.m. UTC | #2
On Freitag, 2. Oktober 2020 13:51:54 CEST Christian Schoenebeck wrote:
> This patch introduces 9pfs test cases using the 9pfs 'local'
> filesystem driver which reads/writes/creates/deletes real files
> and directories.
> 
> In this initial version, there is only one local test which actually
> only checks if the 9pfs 'local' device was created successfully.
> 
> Before the 9pfs 'local' tests are run, a test directory 'qtest-9p-local'
> is created (with world rwx permissions) under the current working
> directory. At this point that test directory is not auto deleted yet.
> 
> Signed-off-by: Christian Schoenebeck <qemu_oss@crudebyte.com>
> ---
>  tests/qtest/libqos/virtio-9p.c | 100 +++++++++++++++++++++++++++++++++
>  tests/qtest/libqos/virtio-9p.h |   5 ++
>  tests/qtest/virtio-9p-test.c   |  44 ++++++++++-----
>  3 files changed, 135 insertions(+), 14 deletions(-)
> 
> diff --git a/tests/qtest/libqos/virtio-9p.c b/tests/qtest/libqos/virtio-9p.c
> index 2e300063e3..86e40e5d56 100644
> --- a/tests/qtest/libqos/virtio-9p.c
> +++ b/tests/qtest/libqos/virtio-9p.c
> @@ -24,6 +24,63 @@
>  #include "qgraph.h"
> 
>  static QGuestAllocator *alloc;
> +static char *local_test_path;
> +
> +static char *strpr(const char* format, ...) GCC_FMT_ATTR(1, 2);
> +
> +/* Concatenates the passed 2 pathes. Returned result must be freed. */
> +static char *concat_path(const char* a, const char* b)
> +{
> +    const int len = strlen(a) + strlen("/") + strlen(b);
> +    char *path = g_malloc0(len + 1);
> +    snprintf(path, len + 1, "%s/%s", a, b);
> +    g_assert(strlen(path) == len);
> +    return path;
> +}

Ok, but maybe I could make that concat_path() function wrap g_strconcat().

> +
> +/*
> + * Lazy sprintf() implementation which auto allocates buffer. Returned
> result + * must be freed.
> + */
> +static char *strpr(const char* format, ...)
> +{
> +    va_list argp;
> +
> +    va_start(argp, format);
> +    const int sz = vsnprintf(NULL, 0, format, argp) + 1;
> +    va_end(argp);
> +
> +    g_assert(sz > 0);
> +    char *s = g_malloc0(sz);
> +
> +    va_start(argp, format);
> +    const int len = vsnprintf(s, sz, format, argp);
> +    va_end(argp);
> +
> +    g_assert(len + 1 == sz);
> +    return s;
> +}

And this strpr() function entirely be replaced by g_strdup_printf().

> +
> +static void init_local_test_path(void)
> +{
> +    char *pwd = get_current_dir_name();
> +    local_test_path = concat_path(pwd, "qtest-9p-local");
> +    free(pwd);
> +}
> +
> +/* Creates the directory for the 9pfs 'local' filesystem driver to access.
> */ +static void create_local_test_dir(void)
> +{
> +    struct stat st;
> +
> +    g_assert(local_test_path != NULL);
> +    mkdir(local_test_path, 0777);
> +
> +    /* ensure test directory exists now ... */
> +    g_assert(stat(local_test_path, &st) == 0);
> +    /* ... and is actually a directory */
> +    g_assert((st.st_mode & S_IFMT) == S_IFDIR);
> +}
> 
>  static void virtio_9p_cleanup(QVirtio9P *interface)
>  {
> @@ -146,11 +203,54 @@ static void *virtio_9p_pci_create(void *pci_bus,
> QGuestAllocator *t_alloc, return obj;
>  }
> 
> +void virtio_9p_assign_local_driver(GString *cmd_line, const char *args)
> +{
> +    GRegex *regex;
> +    char *s, *arg_repl;
> +
> +    g_assert_nonnull(local_test_path);
> +
> +    /* replace 'synth' driver by 'local' driver */
> +    regex = g_regex_new("-fsdev synth,", 0, 0, NULL);
> +    s = g_regex_replace_literal(
> +        regex, cmd_line->str, -1, 0, "-fsdev local,", 0, NULL
> +    );
> +    g_string_assign(cmd_line, s);
> +    g_free(s);
> +    g_regex_unref(regex);
> +
> +    /* add 'path=...' to '-fsdev ...' group */
> +    regex = g_regex_new("(-fsdev \\w+)(\\s*)", 0, 0, NULL);
> +    arg_repl = strpr("\\1\\2,path='%s'", local_test_path);
> +    s = g_regex_replace(
> +        regex, cmd_line->str, -1, 0, arg_repl, 0, NULL
> +    );
> +    g_string_assign(cmd_line, s);
> +    g_free(arg_repl);
> +    g_free(s);
> +    g_regex_unref(regex);
> +
> +    /* add passed args to '-fsdev ...' group */
> +    regex = g_regex_new("(-fsdev \\w+)(\\s*)", 0, 0, NULL);
> +    arg_repl = strpr("\\1\\2,%s", args);
> +    s = g_regex_replace(
> +        regex, cmd_line->str, -1, 0, arg_repl, 0, NULL
> +    );
> +    g_string_assign(cmd_line, s);
> +    g_free(arg_repl);
> +    g_free(s);
> +    g_regex_unref(regex);
> +}
> +
>  static void virtio_9p_register_nodes(void)
>  {
>      const char *str_simple = "fsdev=fsdev0,mount_tag=" MOUNT_TAG;
>      const char *str_addr = "fsdev=fsdev0,addr=04.0,mount_tag=" MOUNT_TAG;
> 
> +    /* make sure test dir for the 'local' tests exists and is clean */
> +    init_local_test_path();
> +    create_local_test_dir();
> +
>      QPCIAddress addr = {
>          .devfn = QPCI_DEVFN(4, 0),
>      };
> diff --git a/tests/qtest/libqos/virtio-9p.h b/tests/qtest/libqos/virtio-9p.h
> index b1e6badc4a..326a603f72 100644
> --- a/tests/qtest/libqos/virtio-9p.h
> +++ b/tests/qtest/libqos/virtio-9p.h
> @@ -44,4 +44,9 @@ struct QVirtio9PDevice {
>      QVirtio9P v9p;
>  };
> 
> +/**
> + * Prepares QEMU command line for 9pfs tests using the 'local' fs driver.
> + */
> +void virtio_9p_assign_local_driver(GString *cmd_line, const char *args);
> +
>  #endif
> diff --git a/tests/qtest/virtio-9p-test.c b/tests/qtest/virtio-9p-test.c
> index 3281153b9c..af7e169d3a 100644
> --- a/tests/qtest/virtio-9p-test.c
> +++ b/tests/qtest/virtio-9p-test.c
> @@ -895,29 +895,45 @@ static void fs_readdir_split_512(void *obj, void
> *data, fs_readdir_split(obj, data, t_alloc, 512);
>  }
> 
> +static void *assign_9p_local_driver(GString *cmd_line, void *arg)
> +{
> +    virtio_9p_assign_local_driver(cmd_line, "security_model=mapped-xattr");
> +    return arg;
> +}
> +
>  static void register_virtio_9p_test(void)
>  {
> -    qos_add_test("synth/config", "virtio-9p", pci_config, NULL);
> -    qos_add_test("synth/version/basic", "virtio-9p", fs_version, NULL);
> -    qos_add_test("synth/attach/basic", "virtio-9p", fs_attach, NULL);
> -    qos_add_test("synth/walk/basic", "virtio-9p", fs_walk, NULL);
> +
> +    QOSGraphTestOptions opts = {
> +    };
> +
> +    /* 9pfs test cases using the 'synth' filesystem driver */
> +    qos_add_test("synth/config", "virtio-9p", pci_config, &opts);
> +    qos_add_test("synth/version/basic", "virtio-9p", fs_version,  &opts);
> +    qos_add_test("synth/attach/basic", "virtio-9p", fs_attach,  &opts);
> +    qos_add_test("synth/walk/basic", "virtio-9p", fs_walk,  &opts);
>      qos_add_test("synth/walk/no_slash", "virtio-9p", fs_walk_no_slash,
> -                 NULL);
> +                  &opts);
>      qos_add_test("synth/walk/dotdot_from_root", "virtio-9p",
> -                 fs_walk_dotdot, NULL);
> -    qos_add_test("synth/lopen/basic", "virtio-9p", fs_lopen, NULL);
> -    qos_add_test("synth/write/basic", "virtio-9p", fs_write, NULL);
> +                 fs_walk_dotdot,  &opts);
> +    qos_add_test("synth/lopen/basic", "virtio-9p", fs_lopen,  &opts);
> +    qos_add_test("synth/write/basic", "virtio-9p", fs_write,  &opts);
>      qos_add_test("synth/flush/success", "virtio-9p", fs_flush_success,
> -                 NULL);
> +                  &opts);
>      qos_add_test("synth/flush/ignored", "virtio-9p", fs_flush_ignored,
> -                 NULL);
> -    qos_add_test("synth/readdir/basic", "virtio-9p", fs_readdir, NULL);
> +                  &opts);
> +    qos_add_test("synth/readdir/basic", "virtio-9p", fs_readdir,  &opts);
>      qos_add_test("synth/readdir/split_512", "virtio-9p",
> -                 fs_readdir_split_512, NULL);
> +                 fs_readdir_split_512,  &opts);
>      qos_add_test("synth/readdir/split_256", "virtio-9p",
> -                 fs_readdir_split_256, NULL);
> +                 fs_readdir_split_256,  &opts);
>      qos_add_test("synth/readdir/split_128", "virtio-9p",
> -                 fs_readdir_split_128, NULL);
> +                 fs_readdir_split_128,  &opts);
> +
> +
> +    /* 9pfs test cases using the 'local' filesystem driver */
> +    opts.before = assign_9p_local_driver;
> +    qos_add_test("local/config", "virtio-9p", pci_config,  &opts);
>  }
> 
>  libqos_init(register_virtio_9p_test);

Best regards,
Christian Schoenebeck
Daniel P. Berrangé Oct. 2, 2020, 2:35 p.m. UTC | #3
On Fri, Oct 02, 2020 at 04:26:48PM +0200, Christian Schoenebeck wrote:
> On Freitag, 2. Oktober 2020 13:51:54 CEST Christian Schoenebeck wrote:
> > This patch introduces 9pfs test cases using the 9pfs 'local'
> > filesystem driver which reads/writes/creates/deletes real files
> > and directories.
> > 
> > In this initial version, there is only one local test which actually
> > only checks if the 9pfs 'local' device was created successfully.
> > 
> > Before the 9pfs 'local' tests are run, a test directory 'qtest-9p-local'
> > is created (with world rwx permissions) under the current working
> > directory. At this point that test directory is not auto deleted yet.
> > 
> > Signed-off-by: Christian Schoenebeck <qemu_oss@crudebyte.com>
> > ---
> >  tests/qtest/libqos/virtio-9p.c | 100 +++++++++++++++++++++++++++++++++
> >  tests/qtest/libqos/virtio-9p.h |   5 ++
> >  tests/qtest/virtio-9p-test.c   |  44 ++++++++++-----
> >  3 files changed, 135 insertions(+), 14 deletions(-)
> > 
> > diff --git a/tests/qtest/libqos/virtio-9p.c b/tests/qtest/libqos/virtio-9p.c
> > index 2e300063e3..86e40e5d56 100644
> > --- a/tests/qtest/libqos/virtio-9p.c
> > +++ b/tests/qtest/libqos/virtio-9p.c
> > @@ -24,6 +24,63 @@
> >  #include "qgraph.h"
> > 
> >  static QGuestAllocator *alloc;
> > +static char *local_test_path;
> > +
> > +static char *strpr(const char* format, ...) GCC_FMT_ATTR(1, 2);
> > +
> > +/* Concatenates the passed 2 pathes. Returned result must be freed. */
> > +static char *concat_path(const char* a, const char* b)
> > +{
> > +    const int len = strlen(a) + strlen("/") + strlen(b);
> > +    char *path = g_malloc0(len + 1);
> > +    snprintf(path, len + 1, "%s/%s", a, b);
> > +    g_assert(strlen(path) == len);
> > +    return path;
> > +}
> 
> Ok, but maybe I could make that concat_path() function wrap g_strconcat().

Or even one of g_build_path or g_build_filename may be useful

> > +/*
> > + * Lazy sprintf() implementation which auto allocates buffer. Returned
> > result + * must be freed.
> > + */
> > +static char *strpr(const char* format, ...)
> > +{
> > +    va_list argp;
> > +
> > +    va_start(argp, format);
> > +    const int sz = vsnprintf(NULL, 0, format, argp) + 1;
> > +    va_end(argp);
> > +
> > +    g_assert(sz > 0);
> > +    char *s = g_malloc0(sz);
> > +
> > +    va_start(argp, format);
> > +    const int len = vsnprintf(s, sz, format, argp);
> > +    va_end(argp);
> > +
> > +    g_assert(len + 1 == sz);
> > +    return s;
> > +}
> 
> And this strpr() function entirely be replaced by g_strdup_printf().

Yep, its preferrable to use g_strdup_printf instead of manually
allocating.

Regards,
Daniel