details: https://hg.nginx.org/njs/rev/839307cc293a branches: changeset: 1718:839307cc293a user: Dmitry Volyntsev <xei...@nginx.com> date: Fri Oct 08 13:50:50 2021 +0000 description: Tests: added async tests support.
diffstat: src/njs.h | 2 + src/njs_value.c | 14 + src/test/njs_externals_test.c | 150 ++++++++++++++ src/test/njs_externals_test.h | 19 + src/test/njs_unit_test.c | 424 +++++++++++++++++++++++++++++++++++------ 5 files changed, 547 insertions(+), 62 deletions(-) diffs (798 lines): diff -r fb3e13959b71 -r 839307cc293a src/njs.h --- a/src/njs.h Fri Oct 08 13:41:01 2021 +0000 +++ b/src/njs.h Fri Oct 08 13:50:50 2021 +0000 @@ -380,6 +380,7 @@ NJS_EXPORT void njs_vm_memory_error(njs_ NJS_EXPORT void njs_value_undefined_set(njs_value_t *value); NJS_EXPORT void njs_value_null_set(njs_value_t *value); +NJS_EXPORT void njs_value_invalid_set(njs_value_t *value); NJS_EXPORT void njs_value_boolean_set(njs_value_t *value, int yn); NJS_EXPORT void njs_value_number_set(njs_value_t *value, double num); @@ -396,6 +397,7 @@ NJS_EXPORT njs_int_t njs_vm_prop_name(nj NJS_EXPORT njs_int_t njs_value_is_null(const njs_value_t *value); NJS_EXPORT njs_int_t njs_value_is_undefined(const njs_value_t *value); NJS_EXPORT njs_int_t njs_value_is_null_or_undefined(const njs_value_t *value); +NJS_EXPORT njs_int_t njs_value_is_valid(const njs_value_t *value); NJS_EXPORT njs_int_t njs_value_is_boolean(const njs_value_t *value); NJS_EXPORT njs_int_t njs_value_is_number(const njs_value_t *value); NJS_EXPORT njs_int_t njs_value_is_valid_number(const njs_value_t *value); diff -r fb3e13959b71 -r 839307cc293a src/njs_value.c --- a/src/njs_value.c Fri Oct 08 13:41:01 2021 +0000 +++ b/src/njs_value.c Fri Oct 08 13:50:50 2021 +0000 @@ -395,6 +395,13 @@ njs_value_null_set(njs_value_t *value) void +njs_value_invalid_set(njs_value_t *value) +{ + njs_set_invalid(value); +} + + +void njs_value_boolean_set(njs_value_t *value, int yn) { njs_set_boolean(value, yn); @@ -451,6 +458,13 @@ njs_value_is_null_or_undefined(const njs njs_int_t +njs_value_is_valid(const njs_value_t *value) +{ + return njs_is_valid(value); +} + + +njs_int_t njs_value_is_boolean(const njs_value_t *value) { return njs_is_boolean(value); diff -r fb3e13959b71 -r 839307cc293a src/test/njs_externals_test.c --- a/src/test/njs_externals_test.c Fri Oct 08 13:41:01 2021 +0000 +++ b/src/test/njs_externals_test.c Fri Oct 08 13:50:50 2021 +0000 @@ -373,6 +373,71 @@ njs_unit_test_r_method(njs_vm_t *vm, njs static njs_int_t +njs_unit_test_r_subrequest(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, + njs_index_t unused) +{ + njs_vm_event_t vm_event; + njs_function_t *callback; + njs_external_ev_t *ev; + njs_external_env_t *env; + njs_unit_test_req_t *r; + + r = njs_vm_external(vm, njs_external_r_proto_id, njs_argument(args, 0)); + if (r == NULL) { + njs_type_error(vm, "\"this\" is not an external"); + return NJS_ERROR; + } + + callback = njs_value_function(njs_arg(args, nargs, 1)); + if (callback == NULL) { + njs_type_error(vm, "argument is not callable"); + return NJS_ERROR; + } + + vm_event = njs_vm_add_event(vm, callback, 1, NULL, NULL); + if (vm_event == NULL) { + njs_internal_error(vm, "njs_vm_add_event() failed"); + return NJS_ERROR; + } + + ev = njs_mp_alloc(vm->mem_pool, sizeof(njs_external_ev_t)); + if (ev == NULL) { + njs_memory_error(vm); + return NJS_ERROR; + } + + ev->vm_event = vm_event; + ev->data = r; + ev->nargs = 1; + njs_value_assign(&ev->args[0], njs_argument(args, 0)); + + env = vm->external; + + njs_queue_insert_tail(&env->events, &ev->link); + + njs_set_undefined(&vm->retval); + + return NJS_OK; +} + + +static njs_int_t +njs_unit_test_r_retval(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, + njs_index_t unused) +{ + njs_external_env_t *env; + + env = vm->external; + + njs_value_assign(&env->retval, njs_arg(args, nargs, 1)); + + njs_set_undefined(&vm->retval); + + return NJS_OK; +} + + +static njs_int_t njs_unit_test_r_create(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, njs_index_t unused) { @@ -583,6 +648,28 @@ static njs_external_t njs_unit_test_r_e }, { + .flags = NJS_EXTERN_METHOD, + .name.string = njs_str("subrequest"), + .writable = 1, + .configurable = 1, + .enumerable = 1, + .u.method = { + .native = njs_unit_test_r_subrequest, + } + }, + + { + .flags = NJS_EXTERN_METHOD, + .name.string = njs_str("retval"), + .writable = 1, + .configurable = 1, + .enumerable = 1, + .u.method = { + .native = njs_unit_test_r_retval, + } + }, + + { .flags = NJS_EXTERN_OBJECT, .name.string = njs_str("props"), .enumerable = 1, @@ -755,3 +842,66 @@ njs_externals_init(njs_vm_t *vm) { return njs_externals_init_internal(vm, &njs_test_requests[1], 3, 0); } + + +njs_int_t +njs_external_env_init(njs_external_env_t *env) +{ + if (env != NULL) { + njs_value_invalid_set(&env->retval); + njs_queue_init(&env->events); + } + + return NJS_OK; +} + + +njs_int_t +njs_external_process_events(njs_vm_t *vm, njs_external_env_t *env) +{ + njs_queue_t *events; + njs_queue_link_t *link; + njs_external_ev_t *ev; + + events = &env->events; + + for ( ;; ) { + link = njs_queue_first(events); + + if (link == njs_queue_tail(events)) { + break; + } + + ev = njs_queue_link_data(link, njs_external_ev_t, link); + + njs_queue_remove(&ev->link); + ev->link.prev = NULL; + ev->link.next = NULL; + + njs_vm_post_event(vm, ev->vm_event, &ev->args[0], ev->nargs); + } + + return NJS_OK; +} + + +njs_int_t +njs_external_call(njs_vm_t *vm, const njs_str_t *fname, njs_value_t *args, + njs_uint_t nargs) +{ + njs_int_t ret; + njs_function_t *func; + + func = njs_vm_function(vm, fname); + if (func == NULL) { + njs_stderror("njs_external_call(): function \"%V\" not found\n", fname); + return NJS_ERROR; + } + + ret = njs_vm_call(vm, func, args, nargs); + if (ret == NJS_ERROR) { + return NJS_ERROR; + } + + return njs_vm_run(vm); +} diff -r fb3e13959b71 -r 839307cc293a src/test/njs_externals_test.h --- a/src/test/njs_externals_test.h Fri Oct 08 13:41:01 2021 +0000 +++ b/src/test/njs_externals_test.h Fri Oct 08 13:50:50 2021 +0000 @@ -8,8 +8,27 @@ #define _NJS_EXTERNALS_TEST_H_INCLUDED_ +typedef struct { + njs_value_t retval; + njs_queue_t events; /* of njs_external_ev_t */ +} njs_external_env_t; + + +typedef struct { + njs_vm_event_t vm_event; + void *data; + njs_uint_t nargs; + njs_value_t args[3]; + njs_queue_link_t link; +} njs_external_ev_t; + + njs_int_t njs_externals_shared_init(njs_vm_t *vm); njs_int_t njs_externals_init(njs_vm_t *vm); +njs_int_t njs_external_env_init(njs_external_env_t *env); +njs_int_t njs_external_call(njs_vm_t *vm, const njs_str_t *fname, + njs_value_t *args, njs_uint_t nargs); +njs_int_t njs_external_process_events(njs_vm_t *vm, njs_external_env_t *env); #endif /* _NJS_EXTERNALS_TEST_H_INCLUDED_ */ diff -r fb3e13959b71 -r 839307cc293a src/test/njs_unit_test.c --- a/src/test/njs_unit_test.c Fri Oct 08 13:41:01 2021 +0000 +++ b/src/test/njs_unit_test.c Fri Oct 08 13:50:50 2021 +0000 @@ -20486,14 +20486,6 @@ static njs_unit_test_t njs_test[] = "new ctor();"), njs_str("[object AsyncFunction]") }, - { njs_str("let ctor = Object.getPrototypeOf(async function(){}).constructor;" - "let f = new ctor(); f()"), - njs_str("[object Promise]") }, - - { njs_str("let ctor = Object.getPrototypeOf(async function(){}).constructor;" - "let f = new ctor('x', 'await 1; return x'); f(1)"), - njs_str("[object Promise]") }, - { njs_str("let f = new Function('x', 'await 1; return x'); f(1)"), njs_str("SyntaxError: await is only valid in async functions in runtime:1") }, @@ -20510,9 +20502,6 @@ static njs_unit_test_t njs_test[] = "(async function() {f(await 111)})"), njs_str("SyntaxError: await in arguments not supported in 1") }, - { njs_str("Promise.all([async () => [await x('X')]])"), - njs_str("[object Promise]") }, - { njs_str("async () => [await x(1)(),]; async () => [await x(1)()]"), njs_str("[object AsyncFunction]") }, @@ -20937,8 +20926,59 @@ static njs_unit_test_t njs_externals_te { njs_str("$r.buffer instanceof Buffer"), njs_str("true") }, + + { njs_str("let ctor = Object.getPrototypeOf(async function(){}).constructor;" + "let f = new ctor();" + "$r.retval(f())"), + njs_str("[object Promise]") }, + + { njs_str("let ctor = Object.getPrototypeOf(async function(){}).constructor;" + "let f = new ctor('x', 'await 1; return x');" + "$r.retval(f(1))"), + njs_str("[object Promise]") }, + + { njs_str("let ctor = Object.getPrototypeOf(async function(){}).constructor;" + "let f = new ctor('x', 'await 1; return x');" + "f(1).then($r.retval)"), + njs_str("1") }, + + { njs_str("$r.retval(Promise.all([async () => [await x('X')]]))"), + njs_str("[object Promise]") }, + + { njs_str("let obj = { a: 1, b: 2};" + "function cb(r) { r.retval(obj.a); }" + "$r.subrequest(reply => cb(reply))"), + njs_str("1") }, }; + +static njs_unit_test_t njs_async_handler_test[] = +{ + { njs_str("globalThis.main = (function() {" + " function cb(r) { r.retval(1); }" + " function handler(r) {" + " r.subrequest(reply => cb(reply));" + " };" + " return {handler};" + "})();" + ), + njs_str("1") }, + +#if 0 /* FIXME */ + { njs_str("globalThis.main = (function() {" + " let obj = { a: 1, b: 2};" + " function cb(r) { r.retval(obj.a); }" + " function handler(r) {" + " r.subrequest(reply => cb(reply));" + " };" + " return {handler};" + "})();" + ), + njs_str("1") }, +#endif +}; + + static njs_unit_test_t njs_shared_test[] = { { njs_str("var cr = require('crypto'); cr.createHash"), @@ -21531,6 +21571,9 @@ typedef struct { njs_uint_t repeat; njs_bool_t unsafe; njs_bool_t backtrace; + njs_bool_t handler; + njs_bool_t async; + unsigned seed; } njs_opts_t; @@ -21540,6 +21583,27 @@ typedef struct { } njs_stat_t; +typedef struct { + njs_vm_t *vm; + njs_external_env_t *env; + njs_external_env_t env0; + + enum { + sw_start = 0, + sw_handler, + sw_loop, + sw_done + } state; +} njs_external_state_t; + + +typedef struct { + njs_external_state_t *states; + njs_uint_t size; + njs_uint_t current; +} njs_runtime_t; + + static void njs_unit_test_report(njs_str_t *name, njs_stat_t *prev, njs_stat_t *current) { @@ -21555,20 +21619,240 @@ njs_unit_test_report(njs_str_t *name, nj static njs_int_t +njs_external_state_init(njs_vm_t *vm, njs_external_state_t *s, njs_opts_t *opts) +{ + njs_int_t ret; + + if (opts->externals) { + s->env = &s->env0; + + ret = njs_external_env_init(s->env); + if (ret != NJS_OK) { + njs_stderror("njs_external_env_init() failed\n"); + return NJS_ERROR; + } + + } else { + s->env = NULL; + } + + s->vm = njs_vm_clone(vm, s->env); + if (s->vm == NULL) { + njs_stderror("njs_vm_clone() failed\n"); + return NJS_ERROR; + } + + if (opts->externals) { + ret = njs_externals_init(s->vm); + if (ret != NJS_OK) { + njs_stderror("njs_externals_init() failed\n"); + return NJS_ERROR; + } + } + + s->state = sw_start; + + return NJS_OK; +} + + +static njs_int_t +njs_external_retval(njs_external_state_t *state, njs_str_t *s) +{ + if (state->env != NULL && njs_value_is_valid(&state->env->retval)) { + return njs_vm_value_string(state->vm, s, &state->env->retval); + } + + return njs_vm_retval_string(state->vm, s); +} + + +static njs_runtime_t * +njs_runtime_init(njs_vm_t *vm, njs_opts_t *opts) +{ + njs_int_t ret; + njs_uint_t i; + njs_runtime_t *rt; + + rt = njs_mp_alloc(vm->mem_pool, sizeof(njs_runtime_t)); + if (rt == NULL) { + return NULL; + } + + rt->size = opts->repeat; + rt->states = njs_mp_alloc(vm->mem_pool, + sizeof(njs_external_state_t) * rt->size); + if (rt->states == NULL) { + return NULL; + } + + rt->current = 0; + srandom(opts->seed); + + for (i = 0; i < rt->size; i++) { + ret = njs_external_state_init(vm, &rt->states[i], opts); + if (ret != NJS_OK) { + njs_stderror("njs_external_state_init() failed\n"); + return NULL; + } + } + + return rt; +} + + +static njs_external_state_t * +njs_runtime_next_state(njs_runtime_t *rt, njs_opts_t *opts) +{ + unsigned next, n; + + n = 0; + next = ((opts->async) ? (unsigned) random() : rt->current++) % rt->size; + + while (rt->states[next].state == sw_done) { + next++; + next = next % rt->size; + + n++; + + if (n == rt->size) { + return NULL; + } + } + + return &rt->states[next]; +} + + +static void +njs_runtime_destroy(njs_runtime_t *rt) +{ + njs_uint_t i; + + for (i = 0; i < rt->size; i++) { + if (rt->states[i].vm != NULL) { + njs_vm_destroy(rt->states[i].vm); + } + } +} + + +static njs_int_t +njs_process_test(njs_external_state_t *state, njs_opts_t *opts, + njs_unit_test_t *expected) +{ + njs_int_t ret; + njs_str_t s; + njs_bool_t success; + njs_value_t request; + + static const njs_str_t handler_str = njs_str("main.handler"); + static const njs_str_t request_str = njs_str("$r"); + + switch (state->state) { + case sw_start: + state->state = sw_handler; + + ret = njs_vm_start(state->vm); + if (ret != NJS_OK) { + goto done; + } + + if (opts->async) { + return NJS_OK; + } + + /* Fall through. */ + case sw_handler: + state->state = sw_loop; + + if (opts->handler) { + ret = njs_vm_value(state->vm, &request_str, &request); + if (ret != NJS_OK) { + njs_stderror("njs_vm_value(\"%V\") failed\n", &request_str); + return NJS_ERROR; + } + + ret = njs_external_call(state->vm, &handler_str, &request, 1); + if (ret == NJS_ERROR) { + goto done; + } + + if (opts->async) { + return NJS_OK; + } + } + + /* Fall through. */ + case sw_loop: + default: + for ( ;; ) { + if (!njs_vm_pending(state->vm)) { + break; + } + + ret = njs_external_process_events(state->vm, state->env); + if (ret != NJS_OK) { + njs_stderror("njs_external_process_events() failed\n"); + return NJS_ERROR; + } + + if (njs_vm_waiting(state->vm) && !njs_vm_posted(state->vm)) { + /*TODO: async events. */ + + njs_stderror("njs_process_test(): async events unsupported\n"); + return NJS_ERROR; + } + + (void) njs_vm_run(state->vm); + + if (opts->async) { + return NJS_OK; + } + } + } + +done: + + state->state = sw_done; + + if (njs_external_retval(state, &s) != NJS_OK) { + njs_stderror("njs_external_retval() failed\n"); + return NJS_ERROR; + } + + success = njs_strstr_eq(&expected->ret, &s); + if (!success) { + njs_stderror("njs(\"%V\")\nexpected: \"%V\"\n got: \"%V\"\n", + &expected->script, &expected->ret, &s); + + return NJS_DECLINED; + } + + njs_vm_destroy(state->vm); + state->vm = NULL; + + return NJS_OK; +} + + +static njs_int_t njs_unit_test(njs_unit_test_t tests[], size_t num, njs_str_t *name, njs_opts_t *opts, njs_stat_t *stat) { - u_char *start, *end; - njs_vm_t *vm, *nvm; - njs_int_t ret; - njs_str_t s; - njs_uint_t i, repeat; - njs_stat_t prev; - njs_bool_t success; - njs_vm_opt_t options; + u_char *start, *end; + njs_vm_t *vm; + njs_int_t ret; + njs_str_t s; + njs_bool_t success; + njs_uint_t i; + njs_stat_t prev; + njs_vm_opt_t options; + njs_runtime_t *rt; + njs_external_state_t *state; vm = NULL; - nvm = NULL; + rt = NULL; prev = *stat; @@ -21609,32 +21893,34 @@ njs_unit_test(njs_unit_test_t tests[], s njs_disassembler(vm); } - repeat = opts->repeat; - - do { - if (nvm != NULL) { - njs_vm_destroy(nvm); + rt = njs_runtime_init(vm, opts); + if (rt == NULL) { + njs_stderror("njs_runtime_init() failed\n"); + goto done; + } + + for ( ;; ) { + state = njs_runtime_next_state(rt, opts); + if (state == NULL) { + break; } - nvm = njs_vm_clone(vm, NULL); - if (nvm == NULL) { - njs_printf("njs_vm_clone() failed\n"); + ret = njs_process_test(state, opts, &tests[i]); + if (ret != NJS_OK) { + if (ret == NJS_DECLINED) { + break; + } + + njs_stderror("njs_process_test() failed\n"); goto done; } - - if (opts->externals) { - ret = njs_externals_init(nvm); - if (ret != NJS_OK) { - goto done; - } - } - - ret = njs_vm_start(nvm); - } while (--repeat != 0); - - if (njs_vm_retval_string(nvm, &s) != NJS_OK) { - njs_printf("njs_vm_retval_string() failed\n"); - goto done; + } + + success = (ret == NJS_OK); + + if (rt != NULL) { + njs_runtime_destroy(rt); + rt = NULL; } } else { @@ -21648,23 +21934,20 @@ njs_unit_test(njs_unit_test_t tests[], s s = njs_str_value("Error: " "Extra characters at the end of the script"); } - } - - success = njs_strstr_eq(&tests[i].ret, &s); - - if (!success) { - njs_printf("njs(\"%V\")\nexpected: \"%V\"\n got: \"%V\"\n", - &tests[i].script, &tests[i].ret, &s); - - stat->failed++; + + success = njs_strstr_eq(&tests[i].ret, &s); + if (!success) { + njs_stderror("njs(\"%V\")\nexpected: \"%V\"\n" + " got: \"%V\"\n", + &tests[i].script, &tests[i].ret, &s); + } + } + + if (success) { + stat->passed++; } else { - stat->passed++; - } - - if (nvm != NULL) { - njs_vm_destroy(nvm); - nvm = NULL; + stat->failed++; } njs_vm_destroy(vm); @@ -21675,8 +21958,8 @@ njs_unit_test(njs_unit_test_t tests[], s done: - if (nvm != NULL) { - njs_vm_destroy(nvm); + if (rt != NULL) { + njs_runtime_destroy(rt); } if (vm != NULL) { @@ -22784,7 +23067,7 @@ done: static njs_int_t -njs_get_options(njs_opts_t *opts, int argc, char **argv) +njs_options_parse(njs_opts_t *opts, int argc, char **argv) { char *p; njs_int_t i; @@ -22798,6 +23081,7 @@ njs_get_options(njs_opts_t *opts, int ar " -d print disassembled code.\n" " -f PATTERN1[|PATTERN2..] filter test suites to run.\n" " -r count overrides repeat count for tests.\n" + " -s seed sets seed for async tests.\n" " -v verbose mode.\n"; for (i = 1; i < argc; i++) { @@ -22839,6 +23123,15 @@ njs_get_options(njs_opts_t *opts, int ar njs_stderror("option \"-r\" requires argument\n"); return NJS_ERROR; + case 's': + if (++i < argc) { + opts->seed = atoi(argv[i]); + break; + } + + njs_stderror("option \"-s\" requires argument\n"); + return NJS_ERROR; + case 'v': opts->verbose = 1; break; @@ -22967,8 +23260,14 @@ static njs_test_suite_t njs_suites[] = njs_nitems(njs_externals_test), njs_unit_test }, + { njs_str("async handler"), + { .async = 1, .externals = 1, .handler = 1, .repeat = 4, .seed = 2, .unsafe = 1 }, + njs_async_handler_test, + njs_nitems(njs_async_handler_test), + njs_unit_test }, + { njs_str("shared"), - { .externals = 1, .repeat = 128, .unsafe = 1, .backtrace = 1 }, + { .externals = 1, .repeat = 128, .seed = 42, .unsafe = 1, .backtrace = 1 }, njs_shared_test, njs_nitems(njs_shared_test), njs_unit_test }, @@ -23022,7 +23321,7 @@ main(int argc, char **argv) njs_memzero(&opts, sizeof(njs_opts_t)); - ret = njs_get_options(&opts, argc, argv); + ret = njs_options_parse(&opts, argc, argv); if (ret != NJS_OK) { return (ret == NJS_DONE) ? EXIT_SUCCESS: EXIT_FAILURE; } @@ -23045,6 +23344,7 @@ main(int argc, char **argv) op.disassemble = opts.disassemble; op.repeat = opts.repeat ? opts.repeat : op.repeat; + op.seed = opts.seed ? opts.seed : op.seed; op.verbose = opts.verbose; ret = suite->run(suite->tests, suite->n, &suite->name, &op, &stat); _______________________________________________ nginx-devel mailing list nginx-devel@nginx.org http://mailman.nginx.org/mailman/listinfo/nginx-devel