Patchwork [v5,4/5] coroutine: add check-coroutine automated tests

login
register
mail settings
Submitter Stefan Hajnoczi
Date June 12, 2011, 8:46 p.m.
Message ID <1307911585-22106-5-git-send-email-stefanha@linux.vnet.ibm.com>
Download mbox | patch
Permalink /patch/100114/
State New
Headers show

Comments

Stefan Hajnoczi - June 12, 2011, 8:46 p.m.
To run automated tests for coroutines:

  make check-coroutine
  ./check-coroutine

On success the program terminates with exit status 0.  On failure an
error message is written to stderr and the program exits with exit
status 1.

Signed-off-by: Stefan Hajnoczi <stefanha@linux.vnet.ibm.com>
---
 .gitignore       |    1 +
 Makefile         |    3 +-
 test-coroutine.c |  162 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 165 insertions(+), 1 deletions(-)
 create mode 100644 test-coroutine.c
Andreas Färber - June 25, 2011, 2:11 p.m.
Am 12.06.2011 um 22:46 schrieb Stefan Hajnoczi:

> To run automated tests for coroutines:
>
>  make check-coroutine
>  ./check-coroutine

The commit message doesn't correspond to the changes, it's test- 
coroutine below.

$ ./test-coroutine
/basic/lifecycle: Segmentation fault

Compiles without warnings but crashes at runtime on Darwin/ppc64. It's  
using coroutine-ucontext.o implementation.

(gdb) run
Starting program: /Users/andreas/QEMU/qemu64/test-coroutine
warning: posix_spawn failed, trying execvp, error: 86
Reading symbols for shared libraries +++++++++++++ 
+ 
....................................................................... done
/basic/lifecycle:
Program received signal EXC_BAD_ACCESS, Could not access memory.
Reason: KERN_INVALID_ADDRESS at address: 0x0000000000000000
qemu_coroutine_create (entry=0x2b10 <set_and_exit>) at /Users/andreas/ 
QEMU/qemu/qemu-coroutine.c:23
23	    co->entry = entry;
(gdb)

Andreas

>
> On success the program terminates with exit status 0.  On failure an
> error message is written to stderr and the program exits with exit
> status 1.
>
> Signed-off-by: Stefan Hajnoczi <stefanha@linux.vnet.ibm.com>
> ---
> .gitignore       |    1 +
> Makefile         |    3 +-
> test-coroutine.c |  162 +++++++++++++++++++++++++++++++++++++++++++++ 
> +++++++++
> 3 files changed, 165 insertions(+), 1 deletions(-)
> create mode 100644 test-coroutine.c
>
> diff --git a/.gitignore b/.gitignore
> index 08013fc..3ad334a 100644
> --- a/.gitignore
> +++ b/.gitignore
> @@ -34,6 +34,7 @@ qemu-img-cmds.h
> qemu-io
> qemu-monitor.texi
> QMP/qmp-commands.txt
> +test-coroutine
> .gdbinit
> *.a
> *.aux
> diff --git a/Makefile b/Makefile
> index 306cd9b..6b5899a 100644
> --- a/Makefile
> +++ b/Makefile
> @@ -134,7 +134,7 @@ qemu-io$(EXESUF): qemu-io.o cmd.o qemu-tool.o  
> qemu-error.o $(oslib-obj-y) $(trac
> qemu-img-cmds.h: $(SRC_PATH)/qemu-img-cmds.hx
> 	$(call quiet-command,sh $(SRC_PATH)/scripts/hxtool -h < $< > $@,"   
> GEN   $@")
>
> -check-qint.o check-qstring.o check-qdict.o check-qlist.o check- 
> qfloat.o check-qjson.o: $(GENERATED_HEADERS)
> +check-qint.o check-qstring.o check-qdict.o check-qlist.o check- 
> qfloat.o check-qjson.o test-coroutine.o: $(GENERATED_HEADERS)
>
> CHECK_PROG_DEPS = qemu-malloc.o $(oslib-obj-y) $(trace-obj-y) qemu- 
> tool.o
>
> @@ -144,6 +144,7 @@ check-qdict: check-qdict.o qdict.o qfloat.o  
> qint.o qstring.o qbool.o qlist.o $(C
> check-qlist: check-qlist.o qlist.o qint.o $(CHECK_PROG_DEPS)
> check-qfloat: check-qfloat.o qfloat.o $(CHECK_PROG_DEPS)
> check-qjson: check-qjson.o qfloat.o qint.o qdict.o qstring.o qlist.o  
> qbool.o qjson.o json-streamer.o json-lexer.o json-parser.o error.o  
> qerror.o qemu-error.o $(CHECK_PROG_DEPS)
> +test-coroutine: test-coroutine.o $(coroutine-obj-y) $ 
> (CHECK_PROG_DEPS)
>
> QEMULIBS=libhw32 libhw64 libuser libdis libdis-user
>
> diff --git a/test-coroutine.c b/test-coroutine.c
> new file mode 100644
> index 0000000..9e9d3c9
> --- /dev/null
> +++ b/test-coroutine.c
> @@ -0,0 +1,162 @@
> +/*
> + * Coroutine tests
> + *
> + * Copyright IBM, Corp. 2011
> + *
> + * Authors:
> + *  Stefan Hajnoczi    <stefanha@linux.vnet.ibm.com>
> + *
> + * This work is licensed under the terms of the GNU LGPL, version 2  
> or later.
> + * See the COPYING.LIB file in the top-level directory.
> + *
> + */
> +
> +#include <glib.h>
> +#include "qemu-coroutine.h"
> +
> +/*
> + * Check that qemu_in_coroutine() works
> + */
> +
> +static void coroutine_fn verify_in_coroutine(void *opaque)
> +{
> +    g_assert(qemu_in_coroutine());
> +}
> +
> +static void test_in_coroutine(void)
> +{
> +    Coroutine *coroutine;
> +
> +    g_assert(!qemu_in_coroutine());
> +
> +    coroutine = qemu_coroutine_create(verify_in_coroutine);
> +    qemu_coroutine_enter(coroutine, NULL);
> +}
> +
> +/*
> + * Check that qemu_coroutine_self() works
> + */
> +
> +static void coroutine_fn verify_self(void *opaque)
> +{
> +    g_assert(qemu_coroutine_self() == opaque);
> +}
> +
> +static void test_self(void)
> +{
> +    Coroutine *coroutine;
> +
> +    coroutine = qemu_coroutine_create(verify_self);
> +    qemu_coroutine_enter(coroutine, coroutine);
> +}
> +
> +/*
> + * Check that coroutines may nest multiple levels
> + */
> +
> +typedef struct {
> +    unsigned int n_enter;   /* num coroutines entered */
> +    unsigned int n_return;  /* num coroutines returned */
> +    unsigned int max;       /* maximum level of nesting */
> +} NestData;
> +
> +static void coroutine_fn nest(void *opaque)
> +{
> +    NestData *nd = opaque;
> +
> +    nd->n_enter++;
> +
> +    if (nd->n_enter < nd->max) {
> +        Coroutine *child;
> +
> +        child = qemu_coroutine_create(nest);
> +        qemu_coroutine_enter(child, nd);
> +    }
> +
> +    nd->n_return++;
> +}
> +
> +static void test_nesting(void)
> +{
> +    Coroutine *root;
> +    NestData nd = {
> +        .n_enter  = 0,
> +        .n_return = 0,
> +        .max      = 128,
> +    };
> +
> +    root = qemu_coroutine_create(nest);
> +    qemu_coroutine_enter(root, &nd);
> +
> +    /* Must enter and return from max nesting level */
> +    g_assert_cmpint(nd.n_enter, ==, nd.max);
> +    g_assert_cmpint(nd.n_return, ==, nd.max);
> +}
> +
> +/*
> + * Check that yield/enter transfer control correctly
> + */
> +
> +static void coroutine_fn yield_5_times(void *opaque)
> +{
> +    bool *done = opaque;
> +    int i;
> +
> +    for (i = 0; i < 5; i++) {
> +        qemu_coroutine_yield();
> +    }
> +    *done = true;
> +}
> +
> +static void test_yield(void)
> +{
> +    Coroutine *coroutine;
> +    bool done = false;
> +    int i = -1; /* one extra time to return from coroutine */
> +
> +    coroutine = qemu_coroutine_create(yield_5_times);
> +    while (!done) {
> +        qemu_coroutine_enter(coroutine, &done);
> +        i++;
> +    }
> +    g_assert_cmpint(i, ==, 5); /* coroutine must yield 5 times */
> +}
> +
> +/*
> + * Check that creation, enter, and return work
> + */
> +
> +static void coroutine_fn set_and_exit(void *opaque)
> +{
> +    bool *done = opaque;
> +
> +    *done = true;
> +}
> +
> +static void test_lifecycle(void)
> +{
> +    Coroutine *coroutine;
> +    bool done = false;
> +
> +    /* Create, enter, and return from coroutine */
> +    coroutine = qemu_coroutine_create(set_and_exit);
> +    qemu_coroutine_enter(coroutine, &done);
> +    g_assert(done); /* expect done to be true (first time) */
> +
> +    /* Repeat to check that no state affects this test */
> +    done = false;
> +    coroutine = qemu_coroutine_create(set_and_exit);
> +    qemu_coroutine_enter(coroutine, &done);
> +    g_assert(done); /* expect done to be true (second time) */
> +}
> +
> +int main(int argc, char **argv)
> +{
> +    g_test_init(&argc, &argv, NULL);
> +    g_test_add_func("/basic/lifecycle", test_lifecycle);
> +    g_test_add_func("/basic/yield", test_yield);
> +    g_test_add_func("/basic/nesting", test_nesting);
> +    g_test_add_func("/basic/self", test_self);
> +    g_test_add_func("/basic/in_coroutine", test_in_coroutine);
> +    return g_test_run();
> +}
> -- 
> 1.7.5.3
>
>
Andreas Färber - June 25, 2011, 2:27 p.m.
Am 25.06.2011 um 16:11 schrieb Andreas Färber:

> Am 12.06.2011 um 22:46 schrieb Stefan Hajnoczi:
>
>> To run automated tests for coroutines:
>>
>> make check-coroutine
>> ./check-coroutine
>
> The commit message doesn't correspond to the changes, it's test- 
> coroutine below.
>
> $ ./test-coroutine
> /basic/lifecycle: Segmentation fault
>
> Compiles without warnings but crashes at runtime on Darwin/ppc64.  
> It's using coroutine-ucontext.o implementation.
>
> (gdb) run
> Starting program: /Users/andreas/QEMU/qemu64/test-coroutine
> warning: posix_spawn failed, trying execvp, error: 86
> Reading symbols for shared libraries +++++++++++++ 
> + 
> ....................................................................... done
> /basic/lifecycle:
> Program received signal EXC_BAD_ACCESS, Could not access memory.
> Reason: KERN_INVALID_ADDRESS at address: 0x0000000000000000
> qemu_coroutine_create (entry=0x2b10 <set_and_exit>) at /Users/ 
> andreas/QEMU/qemu/qemu-coroutine.c:23
> 23	    co->entry = entry;
> (gdb)

Commenting out CONFIG_UCONTEXT_COROUTINE=y in config-host.mak to force  
the coroutine-gthread.o implementation, this does not happen:

$ ./test-coroutine
/basic/lifecycle: OK
/basic/yield: OK
/basic/nesting: OK
/basic/self: OK
/basic/in_coroutine: OK

Andreas
Andreas Färber - June 25, 2011, 3:39 p.m.
Am 25.06.2011 um 16:11 schrieb Andreas Färber:

> Am 12.06.2011 um 22:46 schrieb Stefan Hajnoczi:
>
>> To run automated tests for coroutines:
>>
>> make check-coroutine
>> ./check-coroutine
>
> The commit message doesn't correspond to the changes, it's test- 
> coroutine below.
>
> $ ./test-coroutine
> /basic/lifecycle: Segmentation fault
>
> Compiles without warnings but crashes at runtime on Darwin/ppc64.  
> It's using coroutine-ucontext.o implementation.
>
> (gdb) run
> Starting program: /Users/andreas/QEMU/qemu64/test-coroutine
> warning: posix_spawn failed, trying execvp, error: 86
> Reading symbols for shared libraries +++++++++++++ 
> + 
> ....................................................................... done
> /basic/lifecycle:
> Program received signal EXC_BAD_ACCESS, Could not access memory.
> Reason: KERN_INVALID_ADDRESS at address: 0x0000000000000000
> qemu_coroutine_create (entry=0x2b10 <set_and_exit>) at /Users/ 
> andreas/QEMU/qemu/qemu-coroutine.c:23
> 23	    co->entry = entry;
> (gdb)

(gdb) i r
r0             0x3690	13968
r1             0x7fff5fbff1e0	140734799802848
r2             0x7fff70231870	140735074736240
r3             0x0	0
r4             0x10080be00	4303404544
r5             0x0	0
r6             0x2	2
r7             0x0	0
r8             0x8	8
r9             0x0	0
r10            0x7fff80f14fac	140735356686252
r11            0x7fff70237940	140735074761024
r12            0x7fff80f14fa0	140735356686240
r13            0x7fff7028fde8	140735075122664
r14            0x0	0
r15            0x0	0
r16            0x0	0
r17            0x100240e94	4297330324
r18            0x100539040	4300443712
r19            0x100609400	4301296640
r20            0x0	0
r21            0x0	0
r22            0x100609b60	4301298528
r23            0x100240e94	4297330324
r24            0x100240e94	4297330324
r25            0x0	0
r26            0x0	0
r27            0x100205d78	4297088376
r28            0x7fff70231870	140735074736240
r29            0x2b10	11024
r30            0x2b10	11024
r31            0x0	0
pc             0x3690	13968
ps             0x900000000200f030	10376293541495238704
cr             0x24022222	604119586
lr             0x3690	13968
ctr            0x7fff80f14fa0	140735356686240
xer            0x20000000	536870912
mq             0x0	0
fpscr          0x82000000	2181038080
vscr           0x10000	65536
vrsave         0x0	0
(gdb) x/10i ($pc-8)
0x3688 <qemu_coroutine_create+40>:	li      r0,0
0x368c <qemu_coroutine_create+44>:	bl      0x3ca0 <qemu_coroutine_new>
0x3690 <qemu_coroutine_create+48>:	std     r29,0(r3)
0x3694 <qemu_coroutine_create+52>:	nop
0x3698 <qemu_coroutine_create+56>:	nop
0x369c <qemu_coroutine_create+60>:	nop
0x36a0 <qemu_coroutine_create+64>:	ld      r0,120(r1)
0x36a4 <qemu_coroutine_create+68>:	ld      r2,0(r28)
0x36a8 <qemu_coroutine_create+72>:	xor.    r0,r0,r2
0x36ac <qemu_coroutine_create+76>:	li      r2,0
(gdb)

Patch

diff --git a/.gitignore b/.gitignore
index 08013fc..3ad334a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -34,6 +34,7 @@  qemu-img-cmds.h
 qemu-io
 qemu-monitor.texi
 QMP/qmp-commands.txt
+test-coroutine
 .gdbinit
 *.a
 *.aux
diff --git a/Makefile b/Makefile
index 306cd9b..6b5899a 100644
--- a/Makefile
+++ b/Makefile
@@ -134,7 +134,7 @@  qemu-io$(EXESUF): qemu-io.o cmd.o qemu-tool.o qemu-error.o $(oslib-obj-y) $(trac
 qemu-img-cmds.h: $(SRC_PATH)/qemu-img-cmds.hx
 	$(call quiet-command,sh $(SRC_PATH)/scripts/hxtool -h < $< > $@,"  GEN   $@")
 
-check-qint.o check-qstring.o check-qdict.o check-qlist.o check-qfloat.o check-qjson.o: $(GENERATED_HEADERS)
+check-qint.o check-qstring.o check-qdict.o check-qlist.o check-qfloat.o check-qjson.o test-coroutine.o: $(GENERATED_HEADERS)
 
 CHECK_PROG_DEPS = qemu-malloc.o $(oslib-obj-y) $(trace-obj-y) qemu-tool.o
 
@@ -144,6 +144,7 @@  check-qdict: check-qdict.o qdict.o qfloat.o qint.o qstring.o qbool.o qlist.o $(C
 check-qlist: check-qlist.o qlist.o qint.o $(CHECK_PROG_DEPS)
 check-qfloat: check-qfloat.o qfloat.o $(CHECK_PROG_DEPS)
 check-qjson: check-qjson.o qfloat.o qint.o qdict.o qstring.o qlist.o qbool.o qjson.o json-streamer.o json-lexer.o json-parser.o error.o qerror.o qemu-error.o $(CHECK_PROG_DEPS)
+test-coroutine: test-coroutine.o $(coroutine-obj-y) $(CHECK_PROG_DEPS)
 
 QEMULIBS=libhw32 libhw64 libuser libdis libdis-user
 
diff --git a/test-coroutine.c b/test-coroutine.c
new file mode 100644
index 0000000..9e9d3c9
--- /dev/null
+++ b/test-coroutine.c
@@ -0,0 +1,162 @@ 
+/*
+ * Coroutine tests
+ *
+ * Copyright IBM, Corp. 2011
+ *
+ * Authors:
+ *  Stefan Hajnoczi    <stefanha@linux.vnet.ibm.com>
+ *
+ * This work is licensed under the terms of the GNU LGPL, version 2 or later.
+ * See the COPYING.LIB file in the top-level directory.
+ *
+ */
+
+#include <glib.h>
+#include "qemu-coroutine.h"
+
+/*
+ * Check that qemu_in_coroutine() works
+ */
+
+static void coroutine_fn verify_in_coroutine(void *opaque)
+{
+    g_assert(qemu_in_coroutine());
+}
+
+static void test_in_coroutine(void)
+{
+    Coroutine *coroutine;
+
+    g_assert(!qemu_in_coroutine());
+
+    coroutine = qemu_coroutine_create(verify_in_coroutine);
+    qemu_coroutine_enter(coroutine, NULL);
+}
+
+/*
+ * Check that qemu_coroutine_self() works
+ */
+
+static void coroutine_fn verify_self(void *opaque)
+{
+    g_assert(qemu_coroutine_self() == opaque);
+}
+
+static void test_self(void)
+{
+    Coroutine *coroutine;
+
+    coroutine = qemu_coroutine_create(verify_self);
+    qemu_coroutine_enter(coroutine, coroutine);
+}
+
+/*
+ * Check that coroutines may nest multiple levels
+ */
+
+typedef struct {
+    unsigned int n_enter;   /* num coroutines entered */
+    unsigned int n_return;  /* num coroutines returned */
+    unsigned int max;       /* maximum level of nesting */
+} NestData;
+
+static void coroutine_fn nest(void *opaque)
+{
+    NestData *nd = opaque;
+
+    nd->n_enter++;
+
+    if (nd->n_enter < nd->max) {
+        Coroutine *child;
+
+        child = qemu_coroutine_create(nest);
+        qemu_coroutine_enter(child, nd);
+    }
+
+    nd->n_return++;
+}
+
+static void test_nesting(void)
+{
+    Coroutine *root;
+    NestData nd = {
+        .n_enter  = 0,
+        .n_return = 0,
+        .max      = 128,
+    };
+
+    root = qemu_coroutine_create(nest);
+    qemu_coroutine_enter(root, &nd);
+
+    /* Must enter and return from max nesting level */
+    g_assert_cmpint(nd.n_enter, ==, nd.max);
+    g_assert_cmpint(nd.n_return, ==, nd.max);
+}
+
+/*
+ * Check that yield/enter transfer control correctly
+ */
+
+static void coroutine_fn yield_5_times(void *opaque)
+{
+    bool *done = opaque;
+    int i;
+
+    for (i = 0; i < 5; i++) {
+        qemu_coroutine_yield();
+    }
+    *done = true;
+}
+
+static void test_yield(void)
+{
+    Coroutine *coroutine;
+    bool done = false;
+    int i = -1; /* one extra time to return from coroutine */
+
+    coroutine = qemu_coroutine_create(yield_5_times);
+    while (!done) {
+        qemu_coroutine_enter(coroutine, &done);
+        i++;
+    }
+    g_assert_cmpint(i, ==, 5); /* coroutine must yield 5 times */
+}
+
+/*
+ * Check that creation, enter, and return work
+ */
+
+static void coroutine_fn set_and_exit(void *opaque)
+{
+    bool *done = opaque;
+
+    *done = true;
+}
+
+static void test_lifecycle(void)
+{
+    Coroutine *coroutine;
+    bool done = false;
+
+    /* Create, enter, and return from coroutine */
+    coroutine = qemu_coroutine_create(set_and_exit);
+    qemu_coroutine_enter(coroutine, &done);
+    g_assert(done); /* expect done to be true (first time) */
+
+    /* Repeat to check that no state affects this test */
+    done = false;
+    coroutine = qemu_coroutine_create(set_and_exit);
+    qemu_coroutine_enter(coroutine, &done);
+    g_assert(done); /* expect done to be true (second time) */
+}
+
+int main(int argc, char **argv)
+{
+    g_test_init(&argc, &argv, NULL);
+    g_test_add_func("/basic/lifecycle", test_lifecycle);
+    g_test_add_func("/basic/yield", test_yield);
+    g_test_add_func("/basic/nesting", test_nesting);
+    g_test_add_func("/basic/self", test_self);
+    g_test_add_func("/basic/in_coroutine", test_in_coroutine);
+    return g_test_run();
+}