diff options
77 files changed, 1520 insertions, 609 deletions
diff --git a/.gitignore b/.gitignore index 409be555fb..b35a6c1c31 100644 --- a/.gitignore +++ b/.gitignore @@ -179,6 +179,7 @@ make/win32/ # common_test +/lib/common_test/doc/src/ct_slave.xml /lib/common_test/priv/install.sh # compiler diff --git a/erts/doc/src/driver_entry.xml b/erts/doc/src/driver_entry.xml index 7860d83d83..8bdd154cb9 100644 --- a/erts/doc/src/driver_entry.xml +++ b/erts/doc/src/driver_entry.xml @@ -133,7 +133,7 @@ typedef struct erl_drv_entry { int (*control)(ErlDrvData drv_data, unsigned int command, char *buf, int len, char **rbuf, int rlen); /* "ioctl" for drivers - invoked by - port_control/3) */ + port_control/3 */ void (*timeout)(ErlDrvData drv_data); /* Handling of timeout in driver */ void (*outputv)(ErlDrvData drv_data, ErlIOVec *ev); /* called when we have output from erlang @@ -146,7 +146,7 @@ typedef struct erl_drv_entry { before 'stop' can be called */ int (*call)(ErlDrvData drv_data, unsigned int command, char *buf, int len, char **rbuf, int rlen, unsigned int *flags); - /* Works mostly like 'control', a syncronous + /* Works mostly like 'control', a synchronous call into the driver. */ void (*event)(ErlDrvData drv_data, ErlDrvEvent event, ErlDrvEventData event_data); diff --git a/erts/doc/src/erl_nif.xml b/erts/doc/src/erl_nif.xml index 4bbd4e2a54..cdce4ec0b8 100644 --- a/erts/doc/src/erl_nif.xml +++ b/erts/doc/src/erl_nif.xml @@ -688,6 +688,10 @@ typedef enum { <fsummary>Determine if a term is an empty list</fsummary> <desc><p>Return true if <c>term</c> is an empty list.</p></desc> </func> + <marker id="enif_is_exception"/><func><name><ret>int</ret><nametext>enif_is_exception(ErlNifEnv* env, ERL_NIF_TERM term)</nametext></name> + <fsummary>Determine if a term is an exception</fsummary> + <desc><p>Return true if <c>term</c> is an exception.</p></desc> + </func> <func><name><ret>int</ret><nametext>enif_is_fun(ErlNifEnv* env, ERL_NIF_TERM term)</nametext></name> <fsummary>Determine if a term is a fun</fsummary> <desc><p>Return true if <c>term</c> is a fun.</p></desc> @@ -738,7 +742,14 @@ typedef enum { </func> <func><name><ret>ERL_NIF_TERM</ret><nametext>enif_make_badarg(ErlNifEnv* env)</nametext></name> <fsummary>Make a badarg exception.</fsummary> - <desc><p>Make a badarg exception to be returned from a NIF.</p></desc> + <desc><p>Make a badarg exception to be returned from a NIF, and set + an associated exception reason in <c>env</c>. If + <c>enif_make_badarg</c> is called, the term it returns <em>must</em> + be returned from the function that called it. No other return value + is allowed. Also, the term returned from <c>enif_make_badarg</c> may + be passed only to + <seealso marker="#enif_is_exception">enif_is_exception</seealso> and + not to any other NIF API function.</p></desc> </func> <func><name><ret>ERL_NIF_TERM</ret><nametext>enif_make_binary(ErlNifEnv* env, ErlNifBinary* bin)</nametext></name> <fsummary>Make a binary term.</fsummary> diff --git a/erts/doc/src/notes.xml b/erts/doc/src/notes.xml index e91839572b..f5607945a8 100644 --- a/erts/doc/src/notes.xml +++ b/erts/doc/src/notes.xml @@ -30,6 +30,22 @@ </header> <p>This document describes the changes made to the ERTS application.</p> +<section><title>Erts 5.8.3.2</title> + + <section><title>Known Bugs and Problems</title> + <list> + <item> + <p> + Fix halfword emulator bug in <c>ets:select_delete</c> for + <c>ordered_set</c> that caused emulator to crash.</p> + <p> + Own Id: OTP-9258 Aux Id: seq11836 </p> + </item> + </list> + </section> + +</section> + <section><title>Erts 5.8.3.1</title> <section><title>Fixed Bugs and Malfunctions</title> diff --git a/erts/doc/src/run_erl.xml b/erts/doc/src/run_erl.xml index 7bf7f559c5..da08859c7b 100644 --- a/erts/doc/src/run_erl.xml +++ b/erts/doc/src/run_erl.xml @@ -144,6 +144,14 @@ <item>The size (in bytes) of a log file before switching to a new log file. Default is 100000, minimum is 1000 and maximum is approximately 2^30.</item> + <tag>RUN_ERL_DISABLE_FLOWCNTRL</tag> + <item>If defined, disables input and output flow control for the pty opend by run_erl. + Useful if you want to remove any risk of accidentally blocking the flow control + by hit Ctrl-S (instead of Ctrl-D to detach). + Which may result in blocking of the entire beam process + and in the case of running heart as supervisor + even the heart process will be blocked when writing log message to terminal. + Leaving the heart process unable to do its work.</item> </taglist> </section> diff --git a/erts/emulator/beam/bif.c b/erts/emulator/beam/bif.c index 6b1ce823cb..b3325d635b 100644 --- a/erts/emulator/beam/bif.c +++ b/erts/emulator/beam/bif.c @@ -3360,7 +3360,7 @@ BIF_RETTYPE erts_debug_display_1(BIF_ALIST_1) erts_dsprintf_buf_t *dsbufp = erts_create_tmp_dsbuf(64); pres = erts_dsprintf(dsbufp, "%.*T\n", INT_MAX, BIF_ARG_1); if (pres < 0) - erl_exit(1, "Failed to convert term to string: %d (s)\n", + erl_exit(1, "Failed to convert term to string: %d (%s)\n", -pres, erl_errno_id(-pres)); hp = HAlloc(BIF_P, 2*dsbufp->str_len); /* we need length * 2 heap words */ res = buf_to_intlist(&hp, dsbufp->str, dsbufp->str_len, NIL); @@ -3478,7 +3478,7 @@ term2list_dsprintf(Process *p, Eterm term) erts_dsprintf_buf_t *dsbufp = erts_create_tmp_dsbuf(64); pres = erts_dsprintf(dsbufp, "%T", term); if (pres < 0) - erl_exit(1, "Failed to convert term to list: %d (s)\n", + erl_exit(1, "Failed to convert term to list: %d (%s)\n", -pres, erl_errno_id(-pres)); hp = HAlloc(p, 2*dsbufp->str_len); /* we need length * 2 heap words */ res = buf_to_intlist(&hp, dsbufp->str, dsbufp->str_len, NIL); diff --git a/erts/emulator/beam/erl_driver.h b/erts/emulator/beam/erl_driver.h index 3c0eade0d8..069b71ab0d 100644 --- a/erts/emulator/beam/erl_driver.h +++ b/erts/emulator/beam/erl_driver.h @@ -294,7 +294,7 @@ typedef struct erl_drv_entry { int (*control)(ErlDrvData drv_data, unsigned int command, char *buf, int len, char **rbuf, int rlen); /* "ioctl" for drivers - invoked by - port_control/3) */ + port_control/3 */ void (*timeout)(ErlDrvData drv_data); /* Handling of timeout in driver */ void (*outputv)(ErlDrvData drv_data, ErlIOVec *ev); /* called when we have output from erlang @@ -307,7 +307,7 @@ typedef struct erl_drv_entry { before 'stop' can be called */ int (*call)(ErlDrvData drv_data, unsigned int command, char *buf, int len, char **rbuf, int rlen, unsigned int *flags); - /* Works mostly like 'control', a syncronous + /* Works mostly like 'control', a synchronous call into the driver. */ void (*event)(ErlDrvData drv_data, ErlDrvEvent event, ErlDrvEventData event_data); diff --git a/erts/emulator/beam/erl_gc.c b/erts/emulator/beam/erl_gc.c index c30d67ac88..5edcd667e7 100644 --- a/erts/emulator/beam/erl_gc.c +++ b/erts/emulator/beam/erl_gc.c @@ -215,7 +215,7 @@ erts_next_heap_size(Uint size, Uint offset) low = mid + 1; } } - erl_exit(1, "no next heap size found: %d, offset %d\n", size, offset); + erl_exit(1, "no next heap size found: %lu, offset %lu\n", (unsigned long)size, (unsigned long)offset); } return 0; } diff --git a/erts/emulator/beam/erl_nif.c b/erts/emulator/beam/erl_nif.c index 135c6b0ccc..8b48444904 100644 --- a/erts/emulator/beam/erl_nif.c +++ b/erts/emulator/beam/erl_nif.c @@ -430,6 +430,11 @@ int enif_is_list(ErlNifEnv* env, ERL_NIF_TERM term) return is_list(term) || is_nil(term); } +int enif_is_exception(ErlNifEnv* env, ERL_NIF_TERM term) +{ + return term == THE_NON_VALUE; +} + static void aligned_binary_dtor(struct enif_tmp_obj_t* obj) { erts_free_aligned_binary_bytes_extra((byte*)obj,ERTS_ALC_T_TMP); diff --git a/erts/emulator/beam/erl_nif.h b/erts/emulator/beam/erl_nif.h index 8050b3640a..d028567faf 100644 --- a/erts/emulator/beam/erl_nif.h +++ b/erts/emulator/beam/erl_nif.h @@ -31,9 +31,10 @@ ** 1.0: R13B04 ** 2.0: R14A ** 2.1: R14B02 "vm_variant" +** 2.2: R14B03 enif_is_exception */ #define ERL_NIF_MAJOR_VERSION 2 -#define ERL_NIF_MINOR_VERSION 1 +#define ERL_NIF_MINOR_VERSION 2 #include <stdlib.h> diff --git a/erts/emulator/beam/erl_nif_api_funcs.h b/erts/emulator/beam/erl_nif_api_funcs.h index eca506593d..c991b61abe 100644 --- a/erts/emulator/beam/erl_nif_api_funcs.h +++ b/erts/emulator/beam/erl_nif_api_funcs.h @@ -1,7 +1,7 @@ /* * %CopyrightBegin% * - * Copyright Ericsson AB 2009-2010. All Rights Reserved. + * Copyright Ericsson AB 2009-2011. All Rights Reserved. * * The contents of this file are subject to the Erlang Public License, * Version 1.1, (the "License"); you may not use this file except in @@ -21,6 +21,13 @@ # error This file should not be included directly #endif +/* +** WARNING: add new ERL_NIF_API_FUNC_DECL entries at the bottom of the list +** to keep compatibility on Windows!!! +** +** And don't forget to increase ERL_NIF_MINOR_VERSION in erl_nif.h +** when adding functions to the API. +*/ #ifdef ERL_NIF_API_FUNC_DECL ERL_NIF_API_FUNC_DECL(void*,enif_priv_data,(ErlNifEnv*)); ERL_NIF_API_FUNC_DECL(void*,enif_alloc,(size_t size)); @@ -128,12 +135,17 @@ ERL_NIF_API_FUNC_DECL(int,enif_get_uint64,(ErlNifEnv*, ERL_NIF_TERM term, ErlNif ERL_NIF_API_FUNC_DECL(ERL_NIF_TERM,enif_make_int64,(ErlNifEnv*, ErlNifSInt64)); ERL_NIF_API_FUNC_DECL(ERL_NIF_TERM,enif_make_uint64,(ErlNifEnv*, ErlNifUInt64)); #endif +ERL_NIF_API_FUNC_DECL(int,enif_is_exception,(ErlNifEnv*, ERL_NIF_TERM term)); /* -** Add last to keep compatibility on Windows!!! +** Add new entries here to keep compatibility on Windows!!! */ #endif +/* +** Please keep the ERL_NIF_API_FUNC_MACRO list below in the same order +** as the ERL_NIF_API_FUNC_DECL list above +*/ #ifdef ERL_NIF_API_FUNC_MACRO # define enif_priv_data ERL_NIF_API_FUNC_MACRO(enif_priv_data) # define enif_alloc ERL_NIF_API_FUNC_MACRO(enif_alloc) @@ -243,6 +255,11 @@ ERL_NIF_API_FUNC_DECL(ERL_NIF_TERM,enif_make_uint64,(ErlNifEnv*, ErlNifUInt64)); # define enif_make_uint64 ERL_NIF_API_FUNC_MACRO(enif_make_uint64) #endif +# define enif_is_exception ERL_NIF_API_FUNC_MACRO(enif_is_exception) + +/* +** Add new entries here +*/ #endif #ifndef enif_make_list1 diff --git a/erts/emulator/beam/utils.c b/erts/emulator/beam/utils.c index f531d1430b..6b4f3b3b36 100644 --- a/erts/emulator/beam/utils.c +++ b/erts/emulator/beam/utils.c @@ -2705,7 +2705,7 @@ term_array: /* arrays in 'aa' and 'bb', length in 'i' */ while (--i) { a = *aa++; b = *bb++; - if (a != b) { + if (!is_same(a,a_base, b,b_base)) { if (is_atom(a) && is_atom(b)) { if ((j = cmp_atoms(a, b)) != 0) { goto not_equal; diff --git a/erts/emulator/drivers/common/efile_drv.c b/erts/emulator/drivers/common/efile_drv.c index 4e9b5005c1..f0ff3f54c5 100644 --- a/erts/emulator/drivers/common/efile_drv.c +++ b/erts/emulator/drivers/common/efile_drv.c @@ -410,7 +410,7 @@ struct t_data static void *ef_safe_alloc(Uint s) { void *p = EF_ALLOC(s); - if (!p) erl_exit(1, "efile drv: Can't allocate %d bytes of memory\n", s); + if (!p) erl_exit(1, "efile drv: Can't allocate %lu bytes of memory\n", (unsigned long)s); return p; } diff --git a/erts/emulator/drivers/common/gzio.c b/erts/emulator/drivers/common/gzio.c index 5531a275ea..741cb6ae20 100644 --- a/erts/emulator/drivers/common/gzio.c +++ b/erts/emulator/drivers/common/gzio.c @@ -632,6 +632,7 @@ erts_gzseek(gzFile file, int offset, int whence) while (s->position < pos) { char buf[512]; int n; + int save_pos = s->position; n = pos - s->position; if (n > sizeof(buf)) @@ -643,6 +644,7 @@ erts_gzseek(gzFile file, int offset, int whence) memset(buf, '\0', n); erts_gzwrite(file, buf, n); } + if (save_pos == s->position) break; } return s->position; diff --git a/erts/emulator/drivers/common/inet_drv.c b/erts/emulator/drivers/common/inet_drv.c index 59f4cfb9b4..aec456151c 100644 --- a/erts/emulator/drivers/common/inet_drv.c +++ b/erts/emulator/drivers/common/inet_drv.c @@ -490,7 +490,6 @@ static int my_strncasecmp(const char *s1, const char *s2, size_t n) #define TCP_REQ_RECV 42 #define TCP_REQ_UNRECV 43 #define TCP_REQ_SHUTDOWN 44 -#define TCP_REQ_MULTI_OP 45 /* UDP and SCTP requests */ #define PACKET_REQ_RECV 60 /* Common for UDP and SCTP */ #define SCTP_REQ_LISTEN 61 /* Different from TCP; not for UDP */ @@ -1763,7 +1762,6 @@ send_async_error(ErlDrvPort port, ErlDrvTermData Port, int Ref, LOAD_INT_CNT + 2*LOAD_TUPLE_CNT]; int i = 0; - i = 0; i = LOAD_ATOM(spec, i, am_inet_async); i = LOAD_PORT(spec, i, Port); i = LOAD_INT(spec, i, Ref); diff --git a/erts/emulator/test/code_SUITE.erl b/erts/emulator/test/code_SUITE.erl index 703a00a598..c1a048be75 100644 --- a/erts/emulator/test/code_SUITE.erl +++ b/erts/emulator/test/code_SUITE.erl @@ -483,7 +483,7 @@ do_false_dependency(Init, Code) -> %% Spawn process. Make sure it has the appropriate init function %% and returned. CP should not contain garbage after the return. Parent = self(), - ?line Pid = spawn_link(fun() -> false_dependency_loop(Parent, Init) end), + ?line Pid = spawn_link(fun() -> false_dependency_loop(Parent, Init, true) end), ?line receive initialized -> ok end, %% Reload the module. Make sure the process is still alive. @@ -501,11 +501,18 @@ do_false_dependency(Init, Code) -> ?line true = erlang:purge_module(cpbugx), ok. -false_dependency_loop(Parent, Init) -> +false_dependency_loop(Parent, Init, SendInitAck) -> Init(), - Parent ! initialized, + case SendInitAck of + true -> Parent ! initialized; + false -> void + %% Just send one init-ack. I guess the point of this test + %% wasn't to fill parents msg-queue (?). Seen to cause + %% out-of-mem (on halfword-vm for some reason) by + %% 91 million msg in queue. /sverker + end, receive - _ -> false_dependency_loop(Parent, Init) + _ -> false_dependency_loop(Parent, Init, false) end. coverage(Config) when is_list(Config) -> diff --git a/erts/emulator/test/nif_SUITE.erl b/erts/emulator/test/nif_SUITE.erl index b79c30d8d9..91d695d979 100644 --- a/erts/emulator/test/nif_SUITE.erl +++ b/erts/emulator/test/nif_SUITE.erl @@ -1121,7 +1121,14 @@ is_checks(Config) when is_list(Config) -> ?line ensure_lib_loaded(Config, 1), ?line ok = check_is(hejsan, <<19,98>>, make_ref(), ok, fun() -> ok end, self(), hd(erlang:ports()), [], [1,9,9,8], - {hejsan, "hejsan", [$h,"ejs",<<"an">>]}). + {hejsan, "hejsan", [$h,"ejs",<<"an">>]}), + try + ?line error = check_is_exception(), + ?line throw(expected_badarg) + catch + error:badarg -> + ?line ok + end. get_length(doc) -> ["Test all enif_get_length functions"]; get_length(Config) when is_list(Config) -> @@ -1245,6 +1252,7 @@ release_resource(_) -> ?nif_stub. last_resource_dtor_call() -> ?nif_stub. make_new_resource(_,_) -> ?nif_stub. check_is(_,_,_,_,_,_,_,_,_,_) -> ?nif_stub. +check_is_exception() -> ?nif_stub. length_test(_,_,_,_,_) -> ?nif_stub. make_atoms() -> ?nif_stub. make_strings() -> ?nif_stub. diff --git a/erts/emulator/test/nif_SUITE_data/nif_SUITE.c b/erts/emulator/test/nif_SUITE_data/nif_SUITE.c index 8489124966..dc047394b5 100644 --- a/erts/emulator/test/nif_SUITE_data/nif_SUITE.c +++ b/erts/emulator/test/nif_SUITE_data/nif_SUITE.c @@ -802,6 +802,23 @@ static ERL_NIF_TERM check_is(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[] } /* + * no arguments + * + * This function is separate from check_is because it calls enif_make_badarg + * and so it must return the badarg exception as its return value. Thus, the + * badarg exception indicates success. Failure is indicated by returning an + * error atom. + */ +static ERL_NIF_TERM check_is_exception(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) +{ + ERL_NIF_TERM error_atom = enif_make_atom(env, "error"); + ERL_NIF_TERM badarg = enif_make_badarg(env); + if (enif_is_exception(env, error_atom)) return error_atom; + if (!enif_is_exception(env, badarg)) return error_atom; + return badarg; +} + +/* * argv[0] atom with length of 6 * argv[1] list with length of 6 * argv[2] empty list @@ -1383,6 +1400,7 @@ static ErlNifFunc nif_funcs[] = {"last_resource_dtor_call", 0, last_resource_dtor_call}, {"make_new_resource", 2, make_new_resource}, {"check_is", 10, check_is}, + {"check_is_exception", 0, check_is_exception}, {"length_test", 5, length_test}, {"make_atoms", 0, make_atoms}, {"make_strings", 0, make_strings}, diff --git a/erts/etc/unix/run_erl.c b/erts/etc/unix/run_erl.c index e97cc14fab..8db8e09bee 100644 --- a/erts/etc/unix/run_erl.c +++ b/erts/etc/unix/run_erl.c @@ -991,9 +991,7 @@ static int open_pty_master(char **ptyslave) static int open_pty_slave(char *name) { int sfd; -#ifdef DEBUG struct termios tty_rmode; -#endif if ((sfd = open(name, O_RDWR, 0)) < 0) { return -1; @@ -1019,6 +1017,25 @@ static int open_pty_slave(char *name) } #endif + if (getenv("RUN_ERL_DISABLE_FLOWCNTRL")) { + if (tcgetattr(sfd, &tty_rmode) < 0) { + fprintf(stderr, "Cannot get terminal's current mode\n"); + exit(-1); + } + + tty_rmode.c_iflag &= ~IXOFF; + if (tcsetattr(sfd, TCSANOW, &tty_rmode) < 0) { + fprintf(stderr, "Cannot disable terminal's flow control on input\n"); + exit(-1); + } + + tty_rmode.c_iflag &= ~IXON; + if (tcsetattr(sfd, TCSANOW, &tty_rmode) < 0) { + fprintf(stderr, "Cannot disable terminal's flow control on output\n"); + exit(-1); + } + } + #ifdef DEBUG if (tcgetattr(sfd, &tty_rmode) < 0) { fprintf(stderr, "Cannot get terminals current mode\n"); diff --git a/erts/preloaded/ebin/prim_file.beam b/erts/preloaded/ebin/prim_file.beam Binary files differindex 4a75e43e73..c2ae01d0d5 100644 --- a/erts/preloaded/ebin/prim_file.beam +++ b/erts/preloaded/ebin/prim_file.beam diff --git a/erts/preloaded/src/prim_file.erl b/erts/preloaded/src/prim_file.erl index 10be852e92..fb0aef8947 100644 --- a/erts/preloaded/src/prim_file.erl +++ b/erts/preloaded/src/prim_file.erl @@ -64,7 +64,7 @@ -include("file.hrl"). -define(DRV, efile). --define(FD_DRV, efile). +-define(FD_DRV, "efile"). -define(LARGEFILESIZE, (1 bsl 63)). @@ -506,7 +506,7 @@ read_file(_) -> %% Takes a Port opened with open/1. read_file(Port, File) when is_port(Port), - (is_list(File) orelse is_binary(File))-> + (is_list(File) orelse is_binary(File)) -> Cmd = [?FILE_READ_FILE | pathname(File)], case drv_command(Port, Cmd) of {error, enomem} -> @@ -832,7 +832,7 @@ drv_open(Driver, Portopts) -> {ok, Port} catch error:Reason -> - {error,Reason} + {error, Reason} end. @@ -1041,8 +1041,8 @@ translate_response(?FILE_RESP_NUMBER, List) -> {N, []} = get_uint64(List), {ok, N}; translate_response(?FILE_RESP_DATA, List) -> - {N, Data} = get_uint64(List), - {ok, {N, Data}}; + {_N, _Data} = ND = get_uint64(List), + {ok, ND}; translate_response(?FILE_RESP_INFO, List) when is_list(List) -> {ok, transform_info_ints(get_uint32s(List))}; translate_response(?FILE_RESP_NUMERR, L0) -> @@ -1087,10 +1087,8 @@ translate_response(?FILE_RESP_FNAME, Data) when is_binary(Data) -> {ok, prim_file:internal_native2name(Data)}; translate_response(?FILE_RESP_FNAME, Data) -> {ok, Data}; - translate_response(?FILE_RESP_ALL_DATA, Data) -> {ok, Data}; - translate_response(X, Data) -> {error, {bad_response_from_port, [X | Data]}}. @@ -1137,14 +1135,14 @@ date_to_bytes(undefined) -> date_to_bytes({{Y, Mon, D}, {H, Min, S}}) -> <<Y:32, Mon:32, D:32, H:32, Min:32, S:32>>. -% uint64([[X1, X2, X3, X4] = Y1 | [X5, X6, X7, X8] = Y2]) -> -% (uint32(Y1) bsl 32) bor uint32(Y2). +%% uint64([[X1, X2, X3, X4] = Y1 | [X5, X6, X7, X8] = Y2]) -> +%% (uint32(Y1) bsl 32) bor uint32(Y2). -% uint64(X1, X2, X3, X4, X5, X6, X7, X8) -> -% (uint32(X1, X2, X3, X4) bsl 32) bor uint32(X5, X6, X7, X8). +%% uint64(X1, X2, X3, X4, X5, X6, X7, X8) -> +%% (uint32(X1, X2, X3, X4) bsl 32) bor uint32(X5, X6, X7, X8). -% uint32([X1,X2,X3,X4]) -> -% (X1 bsl 24) bor (X2 bsl 16) bor (X3 bsl 8) bor X4. +%% uint32([X1,X2,X3,X4]) -> +%% (X1 bsl 24) bor (X2 bsl 16) bor (X3 bsl 8) bor X4. uint32(X1,X2,X3,X4) -> (X1 bsl 24) bor (X2 bsl 16) bor (X3 bsl 8) bor X4. diff --git a/lib/common_test/doc/src/common_test_app.xml b/lib/common_test/doc/src/common_test_app.xml index 1ee73b890b..c92566de37 100644 --- a/lib/common_test/doc/src/common_test_app.xml +++ b/lib/common_test/doc/src/common_test_app.xml @@ -296,7 +296,7 @@ </func> <func> - <name>Module:init_per_testcase(TestCase, Config) -> NewConfig | {skip,Reason}</name> + <name>Module:init_per_testcase(TestCase, Config) -> NewConfig | {fail,Reason} | {skip,Reason}</name> <fsummary>Test case initialization.</fsummary> <type> <v> TestCase = atom()</v> @@ -311,10 +311,12 @@ <p>This function is called before each test case. The <c>TestCase</c> argument is the name of the test case, and - <c>Config</c> is the configuration which can be modified - here. Whatever is returned from this function is given as - <c>Config</c> to the test case. If <c>{skip,Reason}</c> is returned, - the test case will be skipped and <c>Reason</c> printed + <c>Config</c> (list of key-value tuples) is the configuration + data that can be modified here. The <c>NewConfig</c> list returned + from this function is given as <c>Config</c> to the test case. + If <c>{fail,Reason}</c> is returned, the test case is + marked as failed without being executed. If <c>{skip,Reason}</c> is + returned, the test case will be skipped and <c>Reason</c> printed in the overview log for the suite.</p> </desc> </func> diff --git a/lib/common_test/doc/src/write_test_chapter.xml b/lib/common_test/doc/src/write_test_chapter.xml index 723492d8f3..3f9fdb7121 100644 --- a/lib/common_test/doc/src/write_test_chapter.xml +++ b/lib/common_test/doc/src/write_test_chapter.xml @@ -167,12 +167,16 @@ returning <c>{fail,Reason}</c>, nor will it be able to save data with <c>{save_config,Data}</c>.</p> - <p>If <c>init_per_testcase</c> crashes, the test case itself is skipped + <p>If <c>init_per_testcase</c> crashes, the test case itself gets skipped automatically (so called <em>auto skipped</em>). If <c>init_per_testcase</c> - returns a <c>skip</c> tuple, also then will the test case be skipped (so - called <em>user skipped</em>). In either event, the <c>end_per_testcase</c> is - never called. + returns a tuple <c>{skip,Reason}</c>, also then the test case gets skipped + (so called <em>user skipped</em>). It is also possible, by returning a tuple + <c>{fail,Reason}</c> from <c>init_per_testcase</c>, to mark the test case + as failed without actually executing it. </p> + <note><p>If <c>init_per_testcase</c> crashes, or returns <c>{skip,Reason}</c> + or <c>{fail,Reason}</c>, the <c>end_per_testcase</c> function is not called. + </p></note> <p>If it is determined during execution of <c>end_per_testcase</c> that the status of a successful test case should be changed to failed, diff --git a/lib/common_test/src/ct.erl b/lib/common_test/src/ct.erl index dfec2b7a67..66da3ef742 100644 --- a/lib/common_test/src/ct.erl +++ b/lib/common_test/src/ct.erl @@ -861,6 +861,7 @@ remove_config(Callback, Config) -> %%% %%% @doc <p>Use this function to set a new timetrap for the running test case.</p> timetrap(Time) -> + test_server:timetrap_cancel(), test_server:timetrap(Time). %%%----------------------------------------------------------------- diff --git a/lib/common_test/src/ct_framework.erl b/lib/common_test/src/ct_framework.erl index 3d4f674160..809616d8e3 100644 --- a/lib/common_test/src/ct_framework.erl +++ b/lib/common_test/src/ct_framework.erl @@ -493,9 +493,9 @@ end_tc(Mod,Func,TCPid,Result,Args,Return) -> case ct_hooks:end_tc( Mod, FuncSpec, Args, Result, Return) of '$ct_no_change' -> - {FinalResult = ok,Result}; - FinalResult -> - {FinalResult,FinalResult} + {ok,Result}; + FinalResult1 -> + {FinalResult1,FinalResult1} end, % send sync notification so that event handlers may print % in the log file before it gets closed @@ -636,7 +636,7 @@ error_notification(Mod,Func,_Args,{Error,Loc}) -> [{?MODULE,error_in_suite}] -> io:format(user, "Error in suite detected: ~s", [ErrStr]); - unknown -> + R when R == unknown; R == undefined -> io:format(user, "Error detected: ~s", [ErrStr]); %% if a function specified by all/0 does not exist, we @@ -737,7 +737,7 @@ get_suite(Mod, Group={conf,Props,_Init,TCs,_End}) -> %% (and only) test case so we can report Error properly [{?MODULE,error_in_suite,[[Error]]}]; [] -> - {error,{invalid_group_spec,Name}}; + []; ConfTests -> case lists:member(skipped, Props) of true -> @@ -767,23 +767,7 @@ get_suite(Mod, Name) -> find_groups(Mod, Name, TCs, GroupDefs) -> Found = find(Mod, Name, TCs, GroupDefs, [], GroupDefs, false), - Trimmed = trim(Found), - %% I cannot find a reason to why this function is called, - %% It deletes any group which is referenced in any other - %% group. i.e. - %% groups() -> - %% [{test, [], [testcase1]}, - %% {testcases, [], [{group, test}]}]. - %% Would be changed to - %% groups() -> - %% [{testcases, [], [testcase1]}]. - %% instead of what I believe is correct: - %% groups() -> - %% [{test, [], [testcase1]}, - %% {testcases, [], [testcase1]}]. - %% Have to double check with peppe - delete_subs(Trimmed, Trimmed), - Trimmed. + trim(Found). find(Mod, all, _TCs, [{Name,Props,Tests} | Gs], Known, Defs, _) when is_atom(Name), is_list(Props), is_list(Tests) -> @@ -1173,12 +1157,14 @@ error_in_suite(Config) -> %% if the group config functions are missing in the suite, %% use these instead ct_init_per_group(GroupName, Config) -> - ct_logs:log("WARNING", "init_per_group/2 for ~w missing in suite, using default.", + ct_logs:log("WARNING", "init_per_group/2 for ~w missing " + "in suite, using default.", [GroupName]), Config. ct_end_per_group(GroupName, _) -> - ct_logs:log("WARNING", "end_per_group/2 for ~w missing in suite, using default.", + ct_logs:log("WARNING", "end_per_group/2 for ~w missing " + "in suite, using default.", [GroupName]), ok. @@ -1187,6 +1173,13 @@ ct_end_per_group(GroupName, _) -> %%% @spec report(What,Data) -> ok report(What,Data) -> case What of + loginfo -> + %% logfiles and direcories have been created for a test and the + %% top level test index page needs to be refreshed + TestName = filename:basename(proplists:get_value(topdir, Data), ".logs"), + RunDir = proplists:get_value(rundir, Data), + ct_logs:make_all_suites_index({TestName,RunDir}), + ok; tests_start -> case ct_util:get_testdata(cover) of undefined -> diff --git a/lib/common_test/src/ct_hooks.erl b/lib/common_test/src/ct_hooks.erl index 5eddefffce..984e04b90f 100644 --- a/lib/common_test/src/ct_hooks.erl +++ b/lib/common_test/src/ct_hooks.erl @@ -122,11 +122,11 @@ end_tc(_Mod, TC, Config, Result, _Return) -> call(fun call_generic/3, Result, [post_end_per_testcase, TC, Config], '$ct_no_change'). -on_tc_skip(How, {_Suite, Case, Reason}) -> - call(fun call_cleanup/3, {How, Reason}, [on_tc_skip, Case]). +on_tc_skip(How, {Suite, Case, Reason}) -> + call(fun call_cleanup/3, {How, Reason}, [on_tc_skip, Suite, Case]). -on_tc_fail(_How, {_Suite, Case, Reason}) -> - call(fun call_cleanup/3, Reason, [on_tc_fail, Case]). +on_tc_fail(_How, {Suite, Case, Reason}) -> + call(fun call_cleanup/3, Reason, [on_tc_fail, Suite, Case]). %% ------------------------------------------------------------------------- %% Internal Functions @@ -145,7 +145,7 @@ call_terminate({Mod, State}, _, _) -> catch_apply(Mod,terminate,[State], ok), {[],{Mod,State}}. -call_cleanup({Mod, State}, Reason, [Function | Args]) -> +call_cleanup({Mod, State}, Reason, [Function, _Suite | Args]) -> NewState = catch_apply(Mod,Function, Args ++ [Reason, State], State), {Reason, {Mod, NewState}}. @@ -229,6 +229,11 @@ scope([post_init_per_suite, SuiteName|_]) -> scope(init) -> none. +terminate_if_scope_ends(HookId, [on_tc_skip,_Suite,{end_per_group,Name}], + Hooks) -> + terminate_if_scope_ends(HookId, [post_end_per_group, Name], Hooks); +terminate_if_scope_ends(HookId, [on_tc_skip,Suite,end_per_suite], Hooks) -> + terminate_if_scope_ends(HookId, [post_end_per_suite, Suite], Hooks); terminate_if_scope_ends(HookId, [Function,Tag|T], Hooks) when T =/= [] -> terminate_if_scope_ends(HookId,[Function,Tag],Hooks); terminate_if_scope_ends(HookId, Function, Hooks) -> diff --git a/lib/common_test/src/ct_logs.erl b/lib/common_test/src/ct_logs.erl index f8ace73cbf..ba4adb8683 100644 --- a/lib/common_test/src/ct_logs.erl +++ b/lib/common_test/src/ct_logs.erl @@ -97,11 +97,11 @@ logdir_node_prefix() -> logdir_prefix()++"."++atom_to_list(node()). %%%----------------------------------------------------------------- -%%% @spec close(How) -> ok +%%% @spec close(Info) -> ok %%% %%% @doc Create index pages with test results and close the CT Log %%% (tool-internal use only). -close(How) -> +close(Info) -> make_last_run_index(), ct_event:notify(#event{name=stop_logging,node=node(),data=[]}), @@ -118,7 +118,7 @@ close(How) -> ok end, - if How == clean -> + if Info == clean -> case cleanup() of ok -> ok; @@ -427,8 +427,8 @@ logger(Parent,Mode) -> file:make_dir(Dir), ct_event:notify(#event{name=start_logging,node=node(), data=?abs(Dir)}), - make_all_suites_index(start), make_all_runs_index(start), + make_all_suites_index(start), case Mode of interactive -> interactive_link(); _ -> ok @@ -796,24 +796,29 @@ make_one_index_entry(SuiteName, LogDir, Label, All, Missing) -> {Succ,Fail,UserSkip,AutoSkip} -> NotBuilt = not_built(SuiteName, LogDir, All, Missing), NewResult = make_one_index_entry1(SuiteName, LogDir, Label, Succ, Fail, - UserSkip, AutoSkip, NotBuilt, All), + UserSkip, AutoSkip, NotBuilt, All, + normal), {NewResult,Succ,Fail,UserSkip,AutoSkip,NotBuilt}; error -> error end. make_one_index_entry1(SuiteName, Link, Label, Success, Fail, UserSkip, AutoSkip, - NotBuilt, All) -> + NotBuilt, All, Mode) -> LogFile = filename:join(Link, ?suitelog_name ++ ".html"), - CrashDumpName = SuiteName ++ "_erl_crash.dump", - CrashDumpLink = - case filelib:is_file(CrashDumpName) of - true -> - [" <A HREF=\"", CrashDumpName, - "\">(CrashDump)</A>"]; - false -> - "" - end, + CrashDumpLink = case Mode of + cached -> + ""; + normal -> + CrashDumpName = SuiteName ++ "_erl_crash.dump", + case filelib:is_file(CrashDumpName) of + true -> + [" <A HREF=\"", CrashDumpName, + "\">(CrashDump)</A>"]; + false -> + "" + end + end, {Lbl,Timestamp,Node,AllInfo} = case All of {true,OldRuns} -> @@ -975,9 +980,13 @@ index_header(Label, StartTime) -> "<th>Missing<br>Suites</th>\n" "\n"]]. + all_suites_index_header() -> {ok,Cwd} = file:get_cwd(), - LogDir = filename:basename(Cwd), + all_suites_index_header(Cwd). + +all_suites_index_header(IndexDir) -> + LogDir = filename:basename(IndexDir), AllRuns = "All test runs in \"" ++ LogDir ++ "\"", [header("Test Results") | ["<CENTER>\n", @@ -1414,15 +1423,72 @@ timestamp(Dir) -> [S,Min,H,D,M,Y] = [list_to_integer(N) || N <- lists:sublist(TsR,6)], format_time({{Y,M,D},{H,Min,S}}). -make_all_suites_index(When) -> +%% ----------------------------- NOTE -------------------------------------- +%% The top level index file is generated based on the file contents under +%% logdir. This takes place initially when the test run starts (When = start) +%% and an update takes place at the end of the test run, or when the user +%% requests an explicit refresh (When = refresh). +%% The index file needs to be updated also at the start of each individual +%% test (in order for the user to be able to track test progress by refreshing +%% the browser). Since it would be too expensive to generate a new file from +%% scratch every time (by reading the data from disk), a copy of the dir tree +%% is cached as a result of the first index file creation. This copy is then +%% used for all top level index page updates that occur during the test run. +%% This means that any changes to the dir tree under logdir during the test +%% run will not show until after the final refresh. +%% ------------------------------------------------------------------------- + +%% Creates the top level index file. When == start | refresh. +%% A copy of the dir tree under logdir is cached as a result. +make_all_suites_index(When) when is_atom(When) -> AbsIndexName = ?abs(?index_name), notify_and_lock_file(AbsIndexName), LogDirs = filelib:wildcard(logdir_prefix()++".*/*"++?logdir_ext), - Sorted = sort_logdirs(LogDirs,[]), - Result = make_all_suites_index1(When,Sorted), + Sorted = sort_logdirs(LogDirs, []), + Result = make_all_suites_index1(When, AbsIndexName, Sorted), notify_and_unlock_file(AbsIndexName), - Result. - + Result; + +%% This updates the top level index file using cached data from +%% the initial index file creation. +make_all_suites_index(NewTestData = {_TestName,DirName}) -> + %% AllLogDirs = [{TestName,Label,Missing,{LastLogDir,Summary},OldDirs}|...] + {AbsIndexName,LogDirData} = ct_util:get_testdata(test_index), + + CtRunDirPos = length(filename:split(AbsIndexName)), + CtRunDir = filename:join(lists:sublist(filename:split(DirName), + CtRunDirPos)), + + Label = case read_totals_file(filename:join(CtRunDir, ?totals_name)) of + {_,"-",_,_} -> "..."; + {_,Lbl,_,_} -> Lbl; + _ -> "..." + end, + notify_and_lock_file(AbsIndexName), + Result = + case catch make_all_suites_ix_cached(AbsIndexName, + NewTestData, + Label, + LogDirData) of + {'EXIT',Reason} -> + io:put_chars("CRASHED while updating " ++ AbsIndexName ++ "!\n"), + io:format("~p~n", [Reason]), + {error,Reason}; + {error,Reason} -> + io:put_chars("FAILED while updating " ++ AbsIndexName ++ "\n"), + io:format("~p~n", [Reason]), + {error,Reason}; + ok -> + ok; + Err -> + io:format("Unknown internal error while updating ~s. " + "Please report.\n(Err: ~p, ID: 1)", + [AbsIndexName,Err]), + {error, Err} + end, + notify_and_unlock_file(AbsIndexName), + Result. + sort_logdirs([Dir|Dirs],Groups) -> TestName = filename:rootname(filename:basename(Dir)), case filelib:wildcard(filename:join(Dir,"run.*")) of @@ -1448,13 +1514,12 @@ sort_each_group([{Test,IxDirs}|Groups]) -> sort_each_group([]) -> []. -make_all_suites_index1(When,AllSuitesLogDirs) -> +make_all_suites_index1(When, AbsIndexName, AllLogDirs) -> IndexName = ?index_name, - AbsIndexName = ?abs(IndexName), if When == start -> ok; true -> io:put_chars("Updating " ++ AbsIndexName ++ "... ") end, - case catch make_all_suites_index2(IndexName,AllSuitesLogDirs) of + case catch make_all_suites_index2(IndexName, AllLogDirs) of {'EXIT', Reason} -> io:put_chars("CRASHED while updating " ++ AbsIndexName ++ "!\n"), io:format("~p~n", [Reason]), @@ -1463,11 +1528,16 @@ make_all_suites_index1(When,AllSuitesLogDirs) -> io:put_chars("FAILED while updating " ++ AbsIndexName ++ "\n"), io:format("~p~n", [Reason]), {error, Reason}; - ok -> - if When == start -> ok; - true -> io:put_chars("done\n") - end, - ok; + {ok,CacheData} -> + case When of + start -> + ct_util:set_testdata_async({test_index,{AbsIndexName, + CacheData}}), + ok; + _ -> + io:put_chars("done\n"), + ok + end; Err -> io:format("Unknown internal error while updating ~s. " "Please report.\n(Err: ~p, ID: 1)", @@ -1475,56 +1545,124 @@ make_all_suites_index1(When,AllSuitesLogDirs) -> {error, Err} end. -make_all_suites_index2(IndexName,AllSuitesLogDirs) -> - {ok,Index0,_Totals} = make_all_suites_index3(AllSuitesLogDirs, - all_suites_index_header(), - 0, 0, 0, 0, 0, []), +make_all_suites_index2(IndexName, AllTestLogDirs) -> + {ok,Index0,_Totals,CacheData} = + make_all_suites_index3(AllTestLogDirs, + all_suites_index_header(), + 0, 0, 0, 0, 0, [], []), Index = [Index0|index_footer()], case force_write_file(IndexName, Index) of ok -> - ok; + {ok,CacheData}; {error, Reason} -> {error,{index_write_error, Reason}} end. -make_all_suites_index3([{SuiteName,[LastLogDir|OldDirs]}|Rest], +make_all_suites_index3([{TestName,[LastLogDir|OldDirs]}|Rest], Result, TotSucc, TotFail, UserSkip, AutoSkip, TotNotBuilt, - Labels) -> + Labels, CacheData) -> [EntryDir|_] = filename:split(LastLogDir), Missing = - case file:read_file(filename:join(EntryDir,?missing_suites_info)) of + case file:read_file(filename:join(EntryDir, ?missing_suites_info)) of {ok,Bin} -> binary_to_term(Bin); _ -> [] end, {Label,Labels1} = case proplists:get_value(EntryDir, Labels) of undefined -> - case read_totals_file(filename:join(EntryDir,?totals_name)) of + case read_totals_file(filename:join(EntryDir, ?totals_name)) of {_,Lbl,_,_} -> {Lbl,[{EntryDir,Lbl}|Labels]}; _ -> {"-",[{EntryDir,"-"}|Labels]} end; Lbl -> {Lbl,Labels} end, - case make_one_index_entry(SuiteName, LastLogDir, Label, {true,OldDirs}, Missing) of + case make_one_index_entry(TestName, LastLogDir, Label, {true,OldDirs}, Missing) of {Result1,Succ,Fail,USkip,ASkip,NotBuilt} -> %% for backwards compatibility AutoSkip1 = case catch AutoSkip+ASkip of {'EXIT',_} -> undefined; Res -> Res end, + IxEntry = {TestName,Label,Missing, + {LastLogDir,{Succ,Fail,USkip,ASkip}},OldDirs}, make_all_suites_index3(Rest, [Result|Result1], TotSucc+Succ, TotFail+Fail, UserSkip+USkip, AutoSkip1, - TotNotBuilt+NotBuilt,Labels1); + TotNotBuilt+NotBuilt, Labels1, + [IxEntry|CacheData]); error -> + IxEntry = {TestName,Label,Missing,{LastLogDir,error},OldDirs}, make_all_suites_index3(Rest, Result, TotSucc, TotFail, - UserSkip, AutoSkip, TotNotBuilt,Labels1) + UserSkip, AutoSkip, TotNotBuilt, Labels1, + [IxEntry|CacheData]) end; make_all_suites_index3([], Result, TotSucc, TotFail, UserSkip, AutoSkip, - TotNotBuilt,_) -> + TotNotBuilt, _, CacheData) -> {ok, [Result|total_row(TotSucc, TotFail, UserSkip, AutoSkip, TotNotBuilt,true)], - {TotSucc,TotFail,UserSkip,AutoSkip,TotNotBuilt}}. + {TotSucc,TotFail,UserSkip,AutoSkip,TotNotBuilt}, lists:reverse(CacheData)}. + + +make_all_suites_ix_cached(AbsIndexName, NewTestData, Label, AllTestLogDirs) -> + AllTestLogDirs1 = insert_new_test_data(NewTestData, Label, AllTestLogDirs), + IndexDir = filename:dirname(AbsIndexName), + Index0 = make_all_suites_ix_cached1(AllTestLogDirs1, + all_suites_index_header(IndexDir), + 0, 0, 0, 0, 0), + Index = [Index0|index_footer()], + case force_write_file(AbsIndexName, Index) of + ok -> + ok; + {error, Reason} -> + {error,{index_write_error, Reason}} + end. + +insert_new_test_data({NewTestName,NewTestDir}, NewLabel, AllTestLogDirs) -> + AllTestLogDirs1 = + case lists:keysearch(NewTestName, 1, AllTestLogDirs) of + {value,{_,_,_,{LastLogDir,_},OldDirs}} -> + [{NewTestName,NewLabel,[],{NewTestDir,{0,0,0,0}}, + [LastLogDir|OldDirs]} | + lists:keydelete(NewTestName, 1, AllTestLogDirs)]; + false -> + [{NewTestName,NewLabel,[],{NewTestDir,{0,0,0,0}},[]} | + AllTestLogDirs] + end, + lists:keysort(1, AllTestLogDirs1). + +make_all_suites_ix_cached1([{TestName,Label,Missing,LastLogDirData,OldDirs}|Rest], + Result, TotSucc, TotFail, UserSkip, AutoSkip, + TotNotBuilt) -> + case make_one_ix_entry_cached(TestName, LastLogDirData, + Label, {true,OldDirs}, Missing) of + {Result1,Succ,Fail,USkip,ASkip,NotBuilt} -> + %% for backwards compatibility + AutoSkip1 = case catch AutoSkip+ASkip of + {'EXIT',_} -> undefined; + Res -> Res + end, + make_all_suites_ix_cached1(Rest, [Result|Result1], TotSucc+Succ, + TotFail+Fail, UserSkip+USkip, AutoSkip1, + TotNotBuilt+NotBuilt); + error -> + make_all_suites_ix_cached1(Rest, Result, TotSucc, TotFail, + UserSkip, AutoSkip, TotNotBuilt) + end; +make_all_suites_ix_cached1([], Result, TotSucc, TotFail, UserSkip, AutoSkip, + TotNotBuilt) -> + [Result|total_row(TotSucc, TotFail, UserSkip, AutoSkip, TotNotBuilt, true)]. + +make_one_ix_entry_cached(TestName, {LogDir,Summary}, Label, All, Missing) -> + case Summary of + {Succ,Fail,UserSkip,AutoSkip} -> + NotBuilt = not_built(TestName, LogDir, All, Missing), + NewResult = make_one_index_entry1(TestName, LogDir, Label, + Succ, Fail, UserSkip, AutoSkip, + NotBuilt, All, cached), + {NewResult,Succ,Fail,UserSkip,AutoSkip,NotBuilt}; + error -> + error + end. %%----------------------------------------------------------------- %% Remove log files. diff --git a/lib/common_test/src/ct_run.erl b/lib/common_test/src/ct_run.erl index 7bd7dc7d66..c01e97b358 100644 --- a/lib/common_test/src/ct_run.erl +++ b/lib/common_test/src/ct_run.erl @@ -262,15 +262,15 @@ run_or_refresh(StartOpts = #opts{logdir = LogDir}, Args) -> %% give the shell time to print version etc timer:sleep(500), io:nl(), - case catch ct_logs:make_all_suites_index(refresh) of - {'EXIT',ASReason} -> + case catch ct_logs:make_all_runs_index(refresh) of + {'EXIT',ARReason} -> file:set_cwd(Cwd), - {error,{all_suites_index,ASReason}}; + {error,{all_runs_index,ARReason}}; _ -> - case catch ct_logs:make_all_runs_index(refresh) of - {'EXIT',ARReason} -> + case catch ct_logs:make_all_suites_index(refresh) of + {'EXIT',ASReason} -> file:set_cwd(Cwd), - {error,{all_runs_index,ARReason}}; + {error,{all_suites_index,ASReason}}; _ -> file:set_cwd(Cwd), io:format("Logs in ~s refreshed!~n~n", [LogDir1]), @@ -1111,6 +1111,8 @@ run(TestDirs) -> install([]), reformat_result(catch do_run(tests(TestDirs), [])). +reformat_result({'EXIT',{user_error,Reason}}) -> + {error,Reason}; reformat_result({user_error,Reason}) -> {error,Reason}; reformat_result(Result) -> diff --git a/lib/common_test/src/ct_util.erl b/lib/common_test/src/ct_util.erl index 115207beed..b3e345b4e5 100644 --- a/lib/common_test/src/ct_util.erl +++ b/lib/common_test/src/ct_util.erl @@ -37,7 +37,7 @@ read_suite_data/1, delete_suite_data/0, delete_suite_data/1, match_delete_suite_data/1, delete_testdata/0, delete_testdata/1, set_testdata/1, get_testdata/1, - update_testdata/2]). + set_testdata_async/1, update_testdata/2]). -export([override_silence_all_connections/0, override_silence_connections/1, get_overridden_silenced_connections/0, @@ -96,7 +96,8 @@ start(Mode,LogDir) -> Pid = spawn_link(fun() -> do_start(S,Mode,LogDir) end), receive {Pid,started} -> Pid; - {Pid,Error} -> exit(Error) + {Pid,Error} -> exit(Error); + {_Ref,{Pid,Error}} -> exit(Error) end; Pid -> case get_mode() of @@ -162,21 +163,19 @@ do_start(Parent,Mode,LogDir) -> end, {StartTime,TestLogDir} = ct_logs:init(Mode), - %% Initiate ct_hooks + ct_event:notify(#event{name=test_start, + node=node(), + data={StartTime, + lists:flatten(TestLogDir)}}), + %% Initialize ct_hooks case catch ct_hooks:init(Opts) of ok -> - ok; + Parent ! {self(),started}; {_,CTHReason} -> ct_logs:tc_print('Suite Callback',CTHReason,[]), - Parent ! {self(), CTHReason}, - self() ! {{stop,normal},{self(),make_ref()}} + self() ! {{stop,{self(),{user_error,CTHReason}}}, + {Parent,make_ref()}} end, - - ct_event:notify(#event{name=test_start, - node=node(), - data={StartTime, - lists:flatten(TestLogDir)}}), - Parent ! {self(),started}, loop(Mode,[],StartDir). create_table(TableName,KeyPos) -> @@ -232,6 +231,9 @@ update_testdata(Key, Fun) -> set_testdata(TestData) -> call({set_testdata, TestData}). +set_testdata_async(TestData) -> + cast({set_testdata, TestData}). + get_testdata(Key) -> call({get_testdata, Key}). @@ -317,7 +319,7 @@ loop(Mode,TestData,StartDir) -> {reset_cwd,From} -> return(From,file:set_cwd(StartDir)), loop(From,TestData,StartDir); - {{stop,How},From} -> + {{stop,Info},From} -> Time = calendar:local_time(), ct_event:sync_notify(#event{name=test_done, node=node(), @@ -330,11 +332,11 @@ loop(Mode,TestData,StartDir) -> ets:delete(?conn_table), ets:delete(?board_table), ets:delete(?suite_table), - ct_logs:close(How), + ct_logs:close(Info), ct_event:stop(), ct_config:stop(), file:set_cwd(StartDir), - return(From,ok); + return(From, Info); {Ref, _Msg} when is_reference(Ref) -> %% This clause is used when doing cast operations. loop(Mode,TestData,StartDir); @@ -537,16 +539,16 @@ reset_silent_connections() -> %%%----------------------------------------------------------------- -%%% @spec stop(How) -> ok +%%% @spec stop(Info) -> ok %%% %%% @doc Stop the ct_util_server and close all existing connections %%% (tool-internal use only). %%% %%% @see ct -stop(How) -> +stop(Info) -> case whereis(ct_util_server) of undefined -> ok; - _ -> call({stop,How}) + _ -> call({stop,Info}) end. %%%----------------------------------------------------------------- diff --git a/lib/common_test/src/vts.erl b/lib/common_test/src/vts.erl index 2ee982d726..081f98e889 100644 --- a/lib/common_test/src/vts.erl +++ b/lib/common_test/src/vts.erl @@ -281,9 +281,7 @@ run_test1(State=#state{tests=Tests,current_log_dir=LogDir}) -> end, unlink(Self) end, - Pid = spawn_link(RunTest), - Total = receive {{test_info,start_info,{_,_,Cases}},From} -> @@ -480,7 +478,7 @@ create_testdir_entries([],_N) -> []. testdir_entry(Dir,Suite,Case,N) -> - NStr = integer_to_list(N), + NStr = vts_integer_to_list(N), tr([td(delete_button(NStr)), td(Dir), td(suite_select(Dir,Suite,NStr)), @@ -691,11 +689,11 @@ result_summary_frame1(State) -> result_summary_body(State) -> N = State#state.ok + State#state.fail + State#state.skip, [h2("Result Summary"), - p([b(integer_to_list(N))," cases executed (of ", - b(integer_to_list(State#state.total)),")"]), - p([green([b(integer_to_list(State#state.ok))," successful"]),br(), - red([b(integer_to_list(State#state.fail))," failed"]),br(), - orange([b(integer_to_list(State#state.skip))," skipped"])]), + p([b(vts_integer_to_list(N))," cases executed (of ", + b(vts_integer_to_list(State#state.total)),")"]), + p([green([b(vts_integer_to_list(State#state.ok))," successful"]),br(), + red([b(vts_integer_to_list(State#state.fail))," failed"]),br(), + orange([b(vts_integer_to_list(State#state.skip))," skipped"])]), executed_test_list(State)]. executed_test_list(#state{testruns=[]}) -> @@ -735,6 +733,14 @@ report1(tc_done,{_Suite,init_per_suite,_},State) -> State; report1(tc_done,{_Suite,end_per_suite,_},State) -> State; +report1(tc_done,{_Suite,init_per_group,_},State) -> + State; +report1(tc_done,{_Suite,end_per_group,_},State) -> + State; +report1(tc_done,{_Suite,ct_init_per_group,_},State) -> + State; +report1(tc_done,{_Suite,ct_end_per_group,_},State) -> + State; report1(tc_done,{_Suite,_Case,ok},State) -> State#state{ok=State#state.ok+1}; report1(tc_done,{_Suite,_Case,{failed,_Reason}},State) -> @@ -742,7 +748,9 @@ report1(tc_done,{_Suite,_Case,{failed,_Reason}},State) -> report1(tc_done,{_Suite,_Case,{skipped,_Reason}},State) -> State#state{skip=State#state.skip+1}; report1(tc_user_skip,{_Suite,_Case,_Reason},State) -> - State#state{skip=State#state.skip+1}. + State#state{skip=State#state.skip+1}; +report1(loginfo,_,State) -> + State. get_test_log(TestName,LogDir) -> [Log] = @@ -882,3 +890,7 @@ get_input_data(Input,Key)-> parse(Input) -> httpd:parse_query(Input). +vts_integer_to_list(X) when is_atom(X) -> + atom_to_list(X); +vts_integer_to_list(X) when is_integer(X) -> + integer_to_list(X). diff --git a/lib/common_test/test/ct_config_SUITE.erl b/lib/common_test/test/ct_config_SUITE.erl index b6b50f33e0..8ce75f582a 100644 --- a/lib/common_test/test/ct_config_SUITE.erl +++ b/lib/common_test/test/ct_config_SUITE.erl @@ -174,7 +174,8 @@ run_test(Name, Config, CTConfig, SuiteNames)-> TestEvents = ct_test_support:get_events(ERPid, Config), ct_test_support:log_events(Name, reformat_events(TestEvents, ?eh), - ?config(config_dir, Config)), + ?config(config_dir, Config), + Opts), ExpEvents = events_to_check(Name), ok = ct_test_support:verify_events(ExpEvents, TestEvents, Config). diff --git a/lib/common_test/test/ct_error_SUITE.erl b/lib/common_test/test/ct_error_SUITE.erl index ad6cf1ba8f..6867e59b60 100644 --- a/lib/common_test/test/ct_error_SUITE.erl +++ b/lib/common_test/test/ct_error_SUITE.erl @@ -102,8 +102,9 @@ cfg_error(Config) when is_list(Config) -> Events = ct_test_support:get_events(ERPid, Config), ct_test_support:log_events(cfg_error, - reformat(Events, ?eh), - ?config(priv_dir, Config)), + reformat(Events, ?eh), + ?config(priv_dir, Config), + Opts), TestEvents = events_to_check(cfg_error), ok = ct_test_support:verify_events(TestEvents, Events, Config). @@ -120,8 +121,9 @@ lib_error(Config) when is_list(Config) -> Events = ct_test_support:get_events(ERPid, Config), ct_test_support:log_events(lib_error, - reformat(Events, ?eh), - ?config(priv_dir, Config)), + reformat(Events, ?eh), + ?config(priv_dir, Config), + Opts), TestEvents = events_to_check(lib_error), ok = ct_test_support:verify_events(TestEvents, Events, Config). @@ -138,8 +140,9 @@ no_compile(Config) when is_list(Config) -> Events = ct_test_support:get_events(ERPid, Config), ct_test_support:log_events(no_compile, - reformat(Events, ?eh), - ?config(priv_dir, Config)), + reformat(Events, ?eh), + ?config(priv_dir, Config), + Opts), TestEvents = events_to_check(no_compile), ok = ct_test_support:verify_events(TestEvents, Events, Config). @@ -156,7 +159,8 @@ timetrap_end_conf(Config) when is_list(Config) -> ct_test_support:log_events(timetrap_end_conf, reformat(Events, ?eh), - ?config(priv_dir, Config)), + ?config(priv_dir, Config), + Opts), TestEvents = events_to_check(timetrap_end_conf), ok = ct_test_support:verify_events(TestEvents, Events, Config). @@ -176,7 +180,8 @@ timetrap_normal(Config) when is_list(Config) -> ct_test_support:log_events(timetrap_normal, reformat(Events, ?eh), - ?config(priv_dir, Config)), + ?config(priv_dir, Config), + Opts), TestEvents = events_to_check(timetrap_normal), ok = ct_test_support:verify_events(TestEvents, Events, Config). @@ -198,12 +203,31 @@ timetrap_extended(Config) when is_list(Config) -> ct_test_support:log_events(timetrap_extended, reformat(Events, ?eh), - ?config(priv_dir, Config)), + ?config(priv_dir, Config), + Opts), TestEvents = events_to_check(timetrap_extended), ok = ct_test_support:verify_events(TestEvents, Events, Config). %%%----------------------------------------------------------------- +%%% +timetrap_parallel(Config) when is_list(Config) -> + DataDir = ?config(data_dir, Config), + Join = fun(D, S) -> filename:join(D, "error/test/"++S) end, + Suite = Join(DataDir, "timetrap_3_SUITE"), + {Opts,ERPid} = setup([{suite,Suite}], Config), + ok = ct_test_support:run(Opts, Config), + Events = ct_test_support:get_events(ERPid, Config), + + ct_test_support:log_events(timetrap_parallel, + reformat(Events, ?eh), + ?config(priv_dir, Config), + Opts), + + TestEvents = events_to_check(timetrap_parallel), + ok = ct_test_support:verify_events(TestEvents, Events, Config). + +%%%----------------------------------------------------------------- %%% HELP FUNCTIONS %%%----------------------------------------------------------------- @@ -236,7 +260,7 @@ test_events(cfg_error) -> [ {?eh,start_logging,{'DEF','RUNDIR'}}, {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}}, - {?eh,start_info,{14,14,42}}, + {?eh,start_info,{14,14,43}}, {?eh,tc_start,{cfg_error_1_SUITE,init_per_suite}}, {?eh,tc_done, @@ -405,7 +429,6 @@ test_events(cfg_error) -> {cfg_error_8_SUITE,{init_per_group,g3,[]}, {failed,{error,{{badmatch,42}, [{cfg_error_8_SUITE,init_per_group,2}, - {cfg_error_8_SUITE,init_per_group,2}, {test_server,my_apply,3}, {test_server,ts_tc,3}, {test_server,run_test_case_eval1,6}, @@ -415,7 +438,6 @@ test_events(cfg_error) -> {failed,{cfg_error_8_SUITE,init_per_group, {'EXIT',{{badmatch,42}, [{cfg_error_8_SUITE,init_per_group,2}, - {cfg_error_8_SUITE,init_per_group,2}, {test_server,my_apply,3}, {test_server,ts_tc,3}, {test_server,run_test_case_eval1,6}, @@ -426,7 +448,6 @@ test_events(cfg_error) -> {failed,{cfg_error_8_SUITE,init_per_group, {'EXIT',{{badmatch,42}, [{cfg_error_8_SUITE,init_per_group,2}, - {cfg_error_8_SUITE,init_per_group,2}, {test_server,my_apply,3}, {test_server,ts_tc,3}, {test_server,run_test_case_eval1,6}, @@ -520,16 +541,19 @@ test_events(cfg_error) -> %%! end_tc failes the testcase {?eh,tc_done,{cfg_error_9_SUITE,tc6,ok}}, {?eh,test_stats,{9,2,{0,18}}}, + {?eh,tc_start,{cfg_error_9_SUITE,tc7}}, + {?eh,tc_done,{cfg_error_9_SUITE,tc7,{failed,{error,tc7_should_be_failed}}}}, + {ct_test_support_eh,test_stats,{9,3,{0,18}}}, {?eh,tc_start,{cfg_error_9_SUITE,tc11}}, {?eh,tc_done,{cfg_error_9_SUITE,tc11, {failed,{cfg_error_9_SUITE,end_per_testcase, {'EXIT',warning_should_be_printed}}}}}, - {?eh,test_stats,{10,2,{0,18}}}, + {?eh,test_stats,{10,3,{0,18}}}, {?eh,tc_start,{cfg_error_9_SUITE,tc12}}, {?eh,tc_done,{cfg_error_9_SUITE,tc12, {failed,{cfg_error_9_SUITE,end_per_testcase, {timetrap_timeout,2000}}}}}, - {?eh,test_stats,{11,2,{0,18}}}, + {?eh,test_stats,{11,3,{0,18}}}, {?eh,tc_start,{cfg_error_9_SUITE,tc13}}, {?eh,tc_done,{cfg_error_9_SUITE,tc13, {failed,{cfg_error_9_SUITE,end_per_testcase, @@ -539,11 +563,11 @@ test_events(cfg_error) -> {test_server,do_end_per_testcase,4}, {test_server,run_test_case_eval1,6}, {test_server,run_test_case_eval,8}]}}}}}}, - {?eh,test_stats,{12,2,{0,18}}}, + {?eh,test_stats,{12,3,{0,18}}}, {?eh,tc_start,{cfg_error_9_SUITE,tc14}}, {?eh,tc_done, {cfg_error_9_SUITE,tc14,{failed,{error,tc14_should_be_failed}}}}, - {?eh,test_stats,{12,3,{0,18}}}, + {?eh,test_stats,{12,4,{0,18}}}, {?eh,tc_start,{cfg_error_9_SUITE,end_per_suite}}, {?eh,tc_done,{cfg_error_9_SUITE,end_per_suite,ok}}, @@ -554,7 +578,7 @@ test_events(cfg_error) -> {?eh,tc_auto_skip,{cfg_error_10_SUITE,tc1, {failed,{cfg_error_10_SUITE,init_per_suite, {failed,fail_init_per_suite}}}}}, - {?eh,test_stats,{12,3,{0,19}}}, + {?eh,test_stats,{12,4,{0,19}}}, {?eh,tc_auto_skip,{cfg_error_10_SUITE,end_per_suite, {failed,{cfg_error_10_SUITE,init_per_suite, {failed,fail_init_per_suite}}}}}, @@ -563,40 +587,40 @@ test_events(cfg_error) -> {?eh,tc_start,{cfg_error_11_SUITE,tc1}}, {?eh,tc_done,{cfg_error_11_SUITE,tc1, {skipped,{config_name_already_in_use,[dummy0]}}}}, - {?eh,test_stats,{12,3,{1,19}}}, + {?eh,test_stats,{12,4,{1,19}}}, {?eh,tc_start,{cfg_error_11_SUITE,tc2}}, {?eh,tc_done,{cfg_error_11_SUITE,tc2,ok}}, - {?eh,test_stats,{13,3,{1,19}}}, + {?eh,test_stats,{13,4,{1,19}}}, {?eh,tc_start,{cfg_error_11_SUITE,end_per_suite}}, {?eh,tc_done,{cfg_error_11_SUITE,end_per_suite,ok}}, {?eh,tc_start,{cfg_error_12_SUITE,tc1}}, {?eh,tc_done,{cfg_error_12_SUITE,tc1,{failed,{timetrap_timeout,500}}}}, - {?eh,test_stats,{13,4,{1,19}}}, + {?eh,test_stats,{13,5,{1,19}}}, {?eh,tc_start,{cfg_error_12_SUITE,tc2}}, {?eh,tc_done,{cfg_error_12_SUITE,tc2,{failed, {cfg_error_12_SUITE,end_per_testcase, {timetrap_timeout,500}}}}}, - {?eh,test_stats,{14,4,{1,19}}}, + {?eh,test_stats,{14,5,{1,19}}}, {?eh,tc_start,{cfg_error_12_SUITE,tc3}}, {?eh,tc_done,{cfg_error_12_SUITE,tc3,ok}}, - {?eh,test_stats,{15,4,{1,19}}}, + {?eh,test_stats,{15,5,{1,19}}}, {?eh,tc_start,{cfg_error_12_SUITE,tc4}}, {?eh,tc_done,{cfg_error_12_SUITE,tc4,{failed, {cfg_error_12_SUITE,end_per_testcase, {timetrap_timeout,500}}}}}, - {?eh,test_stats,{16,4,{1,19}}}, + {?eh,test_stats,{16,5,{1,19}}}, {?eh,tc_start,{cfg_error_13_SUITE,init_per_suite}}, {?eh,tc_done,{cfg_error_13_SUITE,init_per_suite,ok}}, {?eh,tc_start,{cfg_error_13_SUITE,tc1}}, {?eh,tc_done,{cfg_error_13_SUITE,tc1,ok}}, - {?eh,test_stats,{17,4,{1,19}}}, + {?eh,test_stats,{17,5,{1,19}}}, {?eh,tc_start,{cfg_error_13_SUITE,end_per_suite}}, {?eh,tc_done,{cfg_error_13_SUITE,end_per_suite,ok}}, {?eh,tc_start,{cfg_error_14_SUITE,init_per_suite}}, {?eh,tc_done,{cfg_error_14_SUITE,init_per_suite,ok}}, {?eh,tc_start,{cfg_error_14_SUITE,tc1}}, {?eh,tc_done,{cfg_error_14_SUITE,tc1,ok}}, - {?eh,test_stats,{18,4,{1,19}}}, + {?eh,test_stats,{18,5,{1,19}}}, {?eh,tc_start,{cfg_error_14_SUITE,end_per_suite}}, {?eh,tc_done,{cfg_error_14_SUITE,end_per_suite, {comment, @@ -729,7 +753,7 @@ test_events(timetrap_normal) -> [ {?eh,start_logging,{'DEF','RUNDIR'}}, {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}}, - {?eh,start_info,{1,1,3}}, + {?eh,start_info,{1,1,4}}, {?eh,tc_start,{timetrap_2_SUITE,init_per_suite}}, {?eh,tc_done,{timetrap_2_SUITE,init_per_suite,ok}}, {?eh,tc_start,{timetrap_2_SUITE,tc0}}, @@ -744,6 +768,9 @@ test_events(timetrap_normal) -> {?eh,tc_done, {timetrap_2_SUITE,tc2,{failed,{timetrap_timeout,500}}}}, {?eh,test_stats,{0,3,{0,0}}}, + {?eh,tc_start,{timetrap_2_SUITE,tc3}}, + {?eh,tc_done,{timetrap_2_SUITE,tc3,ok}}, + {?eh,test_stats,{1,3,{0,0}}}, {?eh,tc_start,{timetrap_2_SUITE,end_per_suite}}, {?eh,tc_done,{timetrap_2_SUITE,end_per_suite,ok}}, {?eh,test_done,{'DEF','STOP_TIME'}}, @@ -754,7 +781,7 @@ test_events(timetrap_extended) -> [ {?eh,start_logging,{'DEF','RUNDIR'}}, {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}}, - {?eh,start_info,{1,1,3}}, + {?eh,start_info,{1,1,4}}, {?eh,tc_start,{timetrap_2_SUITE,init_per_suite}}, {?eh,tc_done,{timetrap_2_SUITE,init_per_suite,ok}}, {?eh,tc_start,{timetrap_2_SUITE,tc0}}, @@ -769,8 +796,52 @@ test_events(timetrap_extended) -> {?eh,tc_done, {timetrap_2_SUITE,tc2,{failed,{timetrap_timeout,1000}}}}, {?eh,test_stats,{0,3,{0,0}}}, + {?eh,tc_start,{timetrap_2_SUITE,tc3}}, + {?eh,tc_done,{timetrap_2_SUITE,tc3,ok}}, + {?eh,test_stats,{1,3,{0,0}}}, {?eh,tc_start,{timetrap_2_SUITE,end_per_suite}}, {?eh,tc_done,{timetrap_2_SUITE,end_per_suite,ok}}, {?eh,test_done,{'DEF','STOP_TIME'}}, {?eh,stop_logging,[]} - ]. + ]; + +test_events(timetrap_parallel) -> + [ + {?eh,start_logging,{'DEF','RUNDIR'}}, + {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}}, + {?eh,start_info,{1,1,7}}, + {?eh,tc_done,{timetrap_3_SUITE,init_per_suite,ok}}, + {parallel, + [{?eh,tc_start, + {timetrap_3_SUITE,{init_per_group,g1,[parallel]}}}, + {?eh,tc_done, + {timetrap_3_SUITE,{init_per_group,g1,[parallel]},ok}}, + {?eh,tc_start,{timetrap_3_SUITE,tc0}}, + {?eh,tc_start,{timetrap_3_SUITE,tc1}}, + {?eh,tc_start,{timetrap_3_SUITE,tc2}}, + {?eh,tc_start,{timetrap_3_SUITE,tc3}}, + {?eh,tc_start,{timetrap_3_SUITE,tc4}}, + {?eh,tc_start,{timetrap_3_SUITE,tc6}}, + {?eh,tc_start,{timetrap_3_SUITE,tc7}}, + {?eh,tc_done, + {timetrap_3_SUITE,tc1,{failed,{timetrap_timeout,500}}}}, + {?eh,tc_done, + {timetrap_3_SUITE,tc2,{failed,{timetrap_timeout,1000}}}}, + {?eh,tc_done, + {timetrap_3_SUITE,tc6,{failed,{timetrap_timeout,1000}}}}, + {?eh,tc_done, + {timetrap_3_SUITE,tc7,{failed,{timetrap_timeout,1500}}}}, + {?eh,tc_done, + {timetrap_3_SUITE,tc0,{failed,{timetrap_timeout,2000}}}}, + {?eh,tc_done, + {timetrap_3_SUITE,tc4,{failed,{timetrap_timeout,2000}}}}, + {?eh,tc_done, + {timetrap_3_SUITE,tc3,{failed,{timetrap_timeout,3000}}}}, + {?eh,test_stats,{0,7,{0,0}}}, + {?eh,tc_start, + {timetrap_3_SUITE,{end_per_group,g1,[parallel]}}}, + {?eh,tc_done, + {timetrap_3_SUITE,{end_per_group,g1,[parallel]},ok}}]}, + {?eh,tc_done,{timetrap_3_SUITE,end_per_suite,ok}}, + {?eh,test_done,{'DEF','STOP_TIME'}}, + {?eh,stop_logging,[]}]. diff --git a/lib/common_test/test/ct_error_SUITE_data/error/test/cfg_error_9_SUITE.erl b/lib/common_test/test/ct_error_SUITE_data/error/test/cfg_error_9_SUITE.erl index d73287ad62..40b7d2da47 100644 --- a/lib/common_test/test/ct_error_SUITE_data/error/test/cfg_error_9_SUITE.erl +++ b/lib/common_test/test/ct_error_SUITE_data/error/test/cfg_error_9_SUITE.erl @@ -83,6 +83,8 @@ init_per_testcase(tc3, Config) -> Config; init_per_testcase(tc4, _) -> ok; +init_per_testcase(tc7, _) -> + {fail,tc7_should_be_failed}; init_per_testcase(_, Config) -> Config. @@ -136,7 +138,7 @@ groups() -> %% Reason = term() %%-------------------------------------------------------------------- all() -> - [tc1,tc2,tc3,tc4,tc5,tc6, + [tc1,tc2,tc3,tc4,tc5,tc6,tc7, tc11,tc12,tc13,tc14]. tc1(_) -> @@ -171,6 +173,11 @@ tc6(_) -> ct:comment("This one should succeed but then get failed by end_tc!"), fini. +tc7(_) -> + ct:comment("This one should get failed by iptc!"), + fini. + + tc11(_) -> fini. tc12(_) -> diff --git a/lib/common_test/test/ct_error_SUITE_data/error/test/timetrap_2_SUITE.erl b/lib/common_test/test/ct_error_SUITE_data/error/test/timetrap_2_SUITE.erl index 99bb400137..7fcb631d06 100644 --- a/lib/common_test/test/ct_error_SUITE_data/error/test/timetrap_2_SUITE.erl +++ b/lib/common_test/test/ct_error_SUITE_data/error/test/timetrap_2_SUITE.erl @@ -77,8 +77,8 @@ init_per_testcase(tc1, Config) -> ct:timetrap({seconds,1}), Config; -init_per_testcase(tc3, Config) -> - ct:timetrap({seconds,1}), +init_per_testcase(tc2, Config) -> + ct:timetrap(250), Config; init_per_testcase(_TestCase, Config) -> @@ -90,7 +90,7 @@ init_per_testcase(_TestCase, Config) -> %% TestCase = atom() %% Config0 = Config1 = [tuple()] %%-------------------------------------------------------------------- -end_per_testcase(_, Config) -> +end_per_testcase(_, _Config) -> ok. %%-------------------------------------------------------------------- @@ -116,7 +116,7 @@ groups() -> %% Reason = term() %%-------------------------------------------------------------------- all() -> - [tc0,tc1,tc2]. + [tc0,tc1,tc2,tc3]. tc0(_) -> N = list_to_integer(ct:get_config(multiply)), @@ -131,8 +131,24 @@ tc1(_) -> ok. tc2(_) -> + ct:timetrap(500), N = list_to_integer(ct:get_config(multiply)), ct:comment(io_lib:format("TO after ~w sec", [0.5*N])), - ct:timetrap(500), ct:sleep(2000), ok. + +tc3() -> + [{timetrap,{seconds,2}}]. + +tc3(_) -> + T0 = now(), + ct:timetrap(infinity), + N = list_to_integer(ct:get_config(multiply)), + ct:comment(io_lib:format("Sleeping for ~w sec...", [4*N])), + ct:sleep(4000), + Diff = timer:now_diff(now(), T0), + if ((Diff < (N*4000000)) or (Diff > (N*4500000))) -> + exit(not_expected); + true -> + ok + end. diff --git a/lib/common_test/test/ct_event_handler_SUITE.erl b/lib/common_test/test/ct_event_handler_SUITE.erl index 5ef04c0e75..b534a7141d 100644 --- a/lib/common_test/test/ct_event_handler_SUITE.erl +++ b/lib/common_test/test/ct_event_handler_SUITE.erl @@ -102,8 +102,9 @@ start_stop(Config) when is_list(Config) -> Events = ct_test_support:get_events(ERPid, Config), ct_test_support:log_events(start_stop, - ct_test_support:reformat(Events, eh_A), - ?config(priv_dir, Config)), + ct_test_support:reformat(Events, eh_A), + ?config(priv_dir, Config), + Opts), TestEvents = [{eh_A,start_logging,{'DEF','RUNDIR'}}, @@ -148,8 +149,9 @@ results(Config) when is_list(Config) -> Events = ct_test_support:get_events(ERPid, Config), ct_test_support:log_events(results, - ct_test_support:reformat(Events, eh_A), - ?config(priv_dir, Config)), + ct_test_support:reformat(Events, eh_A), + ?config(priv_dir, Config), + Opts), TestEvents = [{eh_A,start_logging,{'DEF','RUNDIR'}}, diff --git a/lib/common_test/test/ct_groups_test_1_SUITE.erl b/lib/common_test/test/ct_groups_test_1_SUITE.erl index 7775d8a55d..e520a72227 100644 --- a/lib/common_test/test/ct_groups_test_1_SUITE.erl +++ b/lib/common_test/test/ct_groups_test_1_SUITE.erl @@ -89,8 +89,9 @@ groups_suite_1(Config) when is_list(Config) -> Events = ct_test_support:get_events(ERPid, Config), ct_test_support:log_events(groups_suite_1, - reformat(Events, ?eh), - ?config(priv_dir, Config)), + reformat(Events, ?eh), + ?config(priv_dir, Config), + Opts), TestEvents = events_to_check(groups_suite_1), ok = ct_test_support:verify_events(TestEvents, Events, Config). @@ -109,8 +110,9 @@ groups_suite_2(Config) when is_list(Config) -> Events = ct_test_support:get_events(ERPid, Config), ct_test_support:log_events(groups_suite_2, - reformat(Events, ?eh), - ?config(priv_dir, Config)), + reformat(Events, ?eh), + ?config(priv_dir, Config), + Opts), TestEvents = events_to_check(groups_suite_2), ok = ct_test_support:verify_events(TestEvents, Events, Config). @@ -130,8 +132,9 @@ groups_suites_1(Config) when is_list(Config) -> Events = ct_test_support:get_events(ERPid, Config), ct_test_support:log_events(groups_suites_1, - reformat(Events, ?eh), - ?config(priv_dir, Config)), + reformat(Events, ?eh), + ?config(priv_dir, Config), + Opts), TestEvents = events_to_check(groups_suites_1), ok = ct_test_support:verify_events(TestEvents, Events, Config). @@ -150,8 +153,9 @@ groups_dir_1(Config) when is_list(Config) -> Events = ct_test_support:get_events(ERPid, Config), ct_test_support:log_events(groups_dir_1, - reformat(Events, ?eh), - ?config(priv_dir, Config)), + reformat(Events, ?eh), + ?config(priv_dir, Config), + Opts), TestEvents = events_to_check(groups_dir_1), ok = ct_test_support:verify_events(TestEvents, Events, Config). @@ -170,8 +174,9 @@ groups_dirs_1(Config) when is_list(Config) -> Events = ct_test_support:get_events(ERPid, Config), ct_test_support:log_events(groups_dirs_1, - reformat(Events, ?eh), - ?config(priv_dir, Config)), + reformat(Events, ?eh), + ?config(priv_dir, Config), + Opts), TestEvents = events_to_check(groups_dirs_1), ok = ct_test_support:verify_events(TestEvents, Events, Config). diff --git a/lib/common_test/test/ct_groups_test_2_SUITE.erl b/lib/common_test/test/ct_groups_test_2_SUITE.erl index 2ae63f4f99..f33be8a9d4 100644 --- a/lib/common_test/test/ct_groups_test_2_SUITE.erl +++ b/lib/common_test/test/ct_groups_test_2_SUITE.erl @@ -59,7 +59,7 @@ end_per_testcase(TestCase, Config) -> suite() -> [{ct_hooks,[ts_install_cth]}]. all() -> - [missing_conf, repeat_1]. + [missing_conf, repeat_1, empty_group]. groups() -> []. @@ -83,13 +83,14 @@ missing_conf(Config) when is_list(Config) -> Suite = filename:join(DataDir, "groups_1/missing_conf_SUITE"), - {Opts,ERPid} = setup({suite,Suite}, Config), + {Opts,ERPid} = setup([{suite,Suite}], Config), ok = ct_test_support:run(Opts, Config), Events = ct_test_support:get_events(ERPid, Config), ct_test_support:log_events(missing_conf_SUITE, - reformat(Events, ?eh), - ?config(priv_dir, Config)), + reformat(Events, ?eh), + ?config(priv_dir, Config), + Opts), TestEvents = events_to_check(missing_conf), ok = ct_test_support:verify_events(TestEvents, Events, Config). @@ -102,18 +103,41 @@ repeat_1(Config) when is_list(Config) -> Suite = filename:join(DataDir, "groups_1/repeat_1_SUITE"), - {Opts,ERPid} = setup({suite,Suite}, Config), + {Opts,ERPid} = setup([{suite,Suite}], Config), ok = ct_test_support:run(Opts, Config), Events = ct_test_support:get_events(ERPid, Config), ct_test_support:log_events(repeat_1, reformat(Events, ?eh), - ?config(priv_dir, Config)), + ?config(priv_dir, Config), + Opts), TestEvents = events_to_check(repeat_1), ok = ct_test_support:verify_events(TestEvents, Events, Config). %%%----------------------------------------------------------------- +%%% + +empty_group(Config) when is_list(Config) -> + DataDir = ?config(data_dir, Config), + + Suite = filename:join(DataDir, "groups_2/groups_22_SUITE"), + + {Opts,ERPid} = setup([{suite,Suite}, + {group,[test_group_8,test_group_9,test_group_10]}], + Config), + ok = ct_test_support:run(Opts, Config), + Events = ct_test_support:get_events(ERPid, Config), + + ct_test_support:log_events(empty_group, + reformat(Events, ?eh), + ?config(priv_dir, Config), + Opts), + + TestEvents = events_to_check(empty_group), + ok = ct_test_support:verify_events(TestEvents, Events, Config). + +%%%----------------------------------------------------------------- %%% HELP FUNCTIONS %%%----------------------------------------------------------------- @@ -121,7 +145,7 @@ setup(Test, Config) -> Opts0 = ct_test_support:get_opts(Config), Level = ?config(trace_level, Config), EvHArgs = [{cbm,ct_test_support},{trace_level,Level}], - Opts = Opts0 ++ [Test,{event_handler,{?eh,EvHArgs}}], + Opts = Opts0 ++ [{event_handler,{?eh,EvHArgs}} | Test], ERPid = ct_test_support:start_event_receiver(Config), {Opts,ERPid}. @@ -256,4 +280,27 @@ test_events(repeat_1) -> {?eh,tc_done,{repeat_1_SUITE,end_per_suite,ok}}, {?eh,test_done,{'DEF','STOP_TIME'}}, {?eh,stop_logging,[]} + ]; + +test_events(empty_group) -> + [{?eh,start_logging,{'DEF','RUNDIR'}}, + {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}}, + {?eh,start_info,{1,1,1}}, + {?eh,tc_start,{groups_22_SUITE,init_per_suite}}, + {?eh,tc_done,{groups_22_SUITE,init_per_suite,ok}}, + [{?eh,tc_start, + {groups_22_SUITE,{init_per_group,test_group_8,[]}}}, + {?eh,tc_done, + {groups_22_SUITE,{init_per_group,test_group_8,[]},ok}}, + {?eh,tc_start,{groups_22_SUITE,testcase_8}}, + {?eh,tc_done,{groups_22_SUITE,testcase_8,ok}}, + {?eh,test_stats,{1,0,{0,0}}}, + {?eh,tc_start, + {groups_22_SUITE,{end_per_group,test_group_8,[]}}}, + {?eh,tc_done, + {groups_22_SUITE,{end_per_group,test_group_8,[]},ok}}], + {?eh,tc_start,{groups_22_SUITE,end_per_suite}}, + {?eh,tc_done,{groups_22_SUITE,end_per_suite,init}}, + {?eh,test_done,{'DEF','STOP_TIME'}}, + {?eh,stop_logging,[]} ]. diff --git a/lib/common_test/test/ct_groups_test_2_SUITE_data/groups_2/groups_22_SUITE.erl b/lib/common_test/test/ct_groups_test_2_SUITE_data/groups_2/groups_22_SUITE.erl index cd517876df..14eb8769ad 100644 --- a/lib/common_test/test/ct_groups_test_2_SUITE_data/groups_2/groups_22_SUITE.erl +++ b/lib/common_test/test/ct_groups_test_2_SUITE_data/groups_2/groups_22_SUITE.erl @@ -31,27 +31,33 @@ suite() -> groups() -> [ - {test_group_1a, [shuffle], [testcase_1a,testcase_1b,testcase_1c]}, + {test_group_1a, [shuffle], [testcase_1a,testcase_1b,testcase_1c]}, - {test_group_1b, [parallel], [testcase_1a,testcase_1b]}, + {test_group_1b, [parallel], [testcase_1a,testcase_1b]}, - {test_group_2, [parallel], [testcase_2a, + {test_group_2, [parallel], [testcase_2a, - {test_group_3, [{repeat,1}], - [testcase_3a, testcase_3b]}, + {test_group_3, [{repeat,1}], + [testcase_3a, testcase_3b]}, - testcase_2b]}, + testcase_2b]}, - {test_group_4, [{test_group_5, [parallel], [testcase_5a, + {test_group_4, [{test_group_5, [parallel], [testcase_5a, - {group, test_group_6}, + {group, test_group_6}, - testcase_5b]}]}, + testcase_5b]}]}, - {test_group_6, [parallel], [{group, test_group_7}]}, + {test_group_6, [parallel], [{group, test_group_7}]}, - {test_group_7, [sequence], [testcase_7a,testcase_7b]} - ]. + {test_group_7, [sequence], [testcase_7a,testcase_7b]}, + + {test_group_8, [], [{group, test_group_9}, testcase_8]}, + + {test_group_9, [], []}, + + {test_group_10, [], [{group, test_group_9}]} + ]. all() -> [{group, test_group_1a}, @@ -60,7 +66,10 @@ all() -> testcase_2, {group, test_group_2}, testcase_3, - {group, test_group_4}]. + {group, test_group_4}, + {group, test_group_8}, + {group, test_group_9}, + {group, test_group_10}]. %% this func only for internal test purposes grs_and_tcs() -> @@ -68,7 +77,9 @@ grs_and_tcs() -> test_group_1a, test_group_1b, test_group_2, test_group_3, test_group_4, test_group_5, - test_group_6, test_group_7 + test_group_6, test_group_7, + test_group_8, test_group_9, + test_group_10 ], [ testcase_1a, testcase_1b, testcase_1c, @@ -78,7 +89,8 @@ grs_and_tcs() -> testcase_3a, testcase_3b, testcase_3, testcase_5a, testcase_5b, - testcase_7a, testcase_7b + testcase_7a, testcase_7b, + testcase_8 ]}. %%-------------------------------------------------------------------- @@ -107,7 +119,10 @@ init_per_group(Group, Config) -> {test_group_4,[{name,test_group_4}]} -> ok; {test_group_5,[{name,test_group_5},parallel]} -> "parallel"; {test_group_6,[{name,test_group_6},parallel]} -> "parallel"; - {test_group_7,[{name,test_group_7},sequence]} -> "sequence" + {test_group_7,[{name,test_group_7},sequence]} -> "sequence"; + {test_group_8,[{name,test_group_8}]} -> ok; + {test_group_9,[{name,test_group_9}]} -> ok; + {test_group_10,[{name,test_group_10}]} -> ok end, {Grs,_} = grs_and_tcs(), case lists:member(Group, Grs) of @@ -312,3 +327,7 @@ testcase_7b(Config) -> undefined = ?config(testcase_7a,Config), testcase_7b = ?config(testcase_7b,Config), ok. +testcase_8() -> + []. +testcase_8(_Config) -> + ok. diff --git a/lib/common_test/test/ct_hooks_SUITE.erl b/lib/common_test/test/ct_hooks_SUITE.erl index 64f4e277ff..8574d7aabc 100644 --- a/lib/common_test/test/ct_hooks_SUITE.erl +++ b/lib/common_test/test/ct_hooks_SUITE.erl @@ -79,7 +79,8 @@ all(suite) -> scope_per_suite_cth, scope_per_group_cth, scope_suite_cth, scope_per_suite_state_cth, scope_per_group_state_cth, scope_suite_state_cth, - fail_pre_suite_cth, fail_post_suite_cth, skip_pre_suite_cth, + fail_pre_suite_cth, double_fail_pre_suite_cth, + fail_post_suite_cth, skip_pre_suite_cth, skip_post_suite_cth, recover_post_suite_cth, update_config_cth, state_update_cth, options_cth, same_id_cth, fail_n_skip_with_minimal_cth @@ -167,6 +168,11 @@ fail_pre_suite_cth(Config) when is_list(Config) -> do_test(fail_pre_suite_cth, "ct_cth_empty_SUITE.erl", [fail_pre_suite_cth],Config). +double_fail_pre_suite_cth(Config) when is_list(Config) -> + do_test(double_fail_pre_suite_cth, "{ct_scope_suite_crash_in_cth_SUITE.erl," + "ct_scope_suite_cth_SUITE.erl}", + [],Config). + fail_post_suite_cth(Config) when is_list(Config) -> do_test(fail_post_suite_cth, "ct_cth_empty_SUITE.erl", [fail_post_suite_cth],Config). @@ -225,8 +231,9 @@ do_test(Tag, SuiteWildCard, CTHs, Config, Res, EC) -> Events = ct_test_support:get_events(ERPid, Config), ct_test_support:log_events(Tag, - reformat(Events, ?eh), - ?config(priv_dir, Config)), + reformat(Events, ?eh), + ?config(priv_dir, Config), + Opts), TestEvents = events_to_check(Tag, EC), ok = ct_test_support:verify_events(TestEvents, Events, Config). @@ -259,9 +266,9 @@ events_to_check(Test, N) -> test_events(one_empty_cth) -> [ {?eh,start_logging,{'DEF','RUNDIR'}}, + {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}}, {?eh,cth,{empty_cth,id,[[]]}}, {?eh,cth,{empty_cth,init,[{'_','_','_'},[]]}}, - {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}}, {?eh,tc_start,{ct_cth_empty_SUITE,init_per_suite}}, {?eh,cth,{empty_cth,pre_init_per_suite, [ct_cth_empty_SUITE,'$proplist',[]]}}, @@ -287,11 +294,11 @@ test_events(one_empty_cth) -> test_events(two_empty_cth) -> [ {?eh,start_logging,{'DEF','RUNDIR'}}, + {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}}, {?eh,cth,{'_',id,[[]]}}, {?eh,cth,{'_',init,['_',[]]}}, {?eh,cth,{'_',id,[[]]}}, {?eh,cth,{'_',init,['_',[]]}}, - {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}}, {?eh,tc_start,{ct_cth_empty_SUITE,init_per_suite}}, {?eh,cth,{'_',pre_init_per_suite,[ct_cth_empty_SUITE,'$proplist',[]]}}, {?eh,cth,{'_',pre_init_per_suite,[ct_cth_empty_SUITE,'$proplist',[]]}}, @@ -329,8 +336,8 @@ test_events(faulty_cth_no_init) -> test_events(faulty_cth_id_no_init) -> [ {?eh,start_logging,{'DEF','RUNDIR'}}, - {?eh,cth,{'_',id,[[]]}}, {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}}, + {?eh,cth,{'_',id,[[]]}}, {negative,{?eh,tc_start,'_'}, {?eh,test_done,{'DEF','STOP_TIME'}}}, {?eh,stop_logging,[]} @@ -339,9 +346,9 @@ test_events(faulty_cth_id_no_init) -> test_events(minimal_cth) -> [ {?eh,start_logging,{'DEF','RUNDIR'}}, + {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}}, {negative,{?eh,cth,{'_',id,['_',[]]}}, {?eh,cth,{'_',init,['_',[]]}}}, - {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}}, {?eh,tc_start,{ct_cth_empty_SUITE,init_per_suite}}, {?eh,tc_done,{ct_cth_empty_SUITE,init_per_suite,ok}}, @@ -357,11 +364,11 @@ test_events(minimal_cth) -> test_events(minimal_and_maximal_cth) -> [ {?eh,start_logging,{'DEF','RUNDIR'}}, + {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}}, {negative,{?eh,cth,{'_',id,['_',[]]}}, {?eh,cth,{'_',init,['_',[]]}}}, {?eh,cth,{'_',id,[[]]}}, {?eh,cth,{'_',init,['_',[]]}}, - {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}}, {?eh,tc_start,{ct_cth_empty_SUITE,init_per_suite}}, {?eh,cth,{'_',pre_init_per_suite,[ct_cth_empty_SUITE,'$proplist',[]]}}, {?eh,cth,{'_',post_init_per_suite,[ct_cth_empty_SUITE,'$proplist','$proplist',[]]}}, @@ -387,8 +394,8 @@ test_events(faulty_cth_undef) -> {failed,FailReasonStr}}, [ {?eh,start_logging,{'DEF','RUNDIR'}}, - {?eh,cth,{'_',init,['_',[]]}}, {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}}, + {?eh,cth,{'_',init,['_',[]]}}, {?eh,tc_start,{ct_cth_empty_SUITE,init_per_suite}}, {?eh,tc_done,{ct_cth_empty_SUITE,init_per_suite, {failed, {error,FailReasonStr}}}}, @@ -433,15 +440,15 @@ test_events(faulty_cth_exit_in_init_scope_suite) -> test_events(faulty_cth_exit_in_init) -> [{?eh,start_logging,{'DEF','RUNDIR'}}, - {?eh,cth,{empty_cth,init,['_',[]]}}, {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}}, + {?eh,cth,{empty_cth,init,['_',[]]}}, {?eh,test_done,{'DEF','STOP_TIME'}}, {?eh,stop_logging,[]}]; test_events(faulty_cth_exit_in_id) -> [{?eh,start_logging,{'DEF','RUNDIR'}}, - {?eh,cth,{empty_cth,id,[[]]}}, {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}}, + {?eh,cth,{empty_cth,id,[[]]}}, {negative, {?eh,tc_start,'_'}, {?eh,test_done,{'DEF','STOP_TIME'}}}, {?eh,stop_logging,[]}]; @@ -609,9 +616,8 @@ test_events(scope_per_group_state_cth) -> test_events(fail_pre_suite_cth) -> [ {?eh,start_logging,{'DEF','RUNDIR'}}, - {?eh,cth,{'_',init,['_',[]]}}, {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}}, - + {?eh,cth,{'_',init,['_',[]]}}, {?eh,tc_start,{ct_cth_empty_SUITE,init_per_suite}}, {?eh,cth,{'_',pre_init_per_suite,[ct_cth_empty_SUITE,'$proplist',[]]}}, @@ -646,11 +652,28 @@ test_events(fail_pre_suite_cth) -> {?eh,stop_logging,[]} ]; -test_events(fail_post_suite_cth) -> +test_events(double_fail_pre_suite_cth) -> [ {?eh,start_logging,{'DEF','RUNDIR'}}, + {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}}, + {?eh,tc_start,{'_',init_per_suite}}, {?eh,cth,{'_',init,['_',[]]}}, + {?eh,cth,{'_',pre_init_per_suite,['_','$proplist',[]]}}, + {?eh,cth,{'_',post_init_per_suite,['_','$proplist', + {fail,"Test failure"},[]]}}, + {?eh,cth, {empty_cth,terminate,[[]]}}, + + {?eh,tc_start,{'_',init_per_suite}}, + {?eh,cth,{'_',init,['_',[]]}}, + {?eh,cth, {empty_cth,terminate,[[]]}}, + {?eh,stop_logging,[]} + ]; + +test_events(fail_post_suite_cth) -> + [ + {?eh,start_logging,{'DEF','RUNDIR'}}, {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}}, + {?eh,cth,{'_',init,['_',[]]}}, {?eh,tc_start,{ct_cth_empty_SUITE,init_per_suite}}, {?eh,cth,{'_',pre_init_per_suite,[ct_cth_empty_SUITE,'$proplist',[]]}}, {?eh,cth,{'_',post_init_per_suite,[ct_cth_empty_SUITE,'$proplist','$proplist',[]]}}, @@ -676,8 +699,8 @@ test_events(fail_post_suite_cth) -> test_events(skip_pre_suite_cth) -> [ {?eh,start_logging,{'DEF','RUNDIR'}}, - {?eh,cth,{'_',init,['_',[]]}}, {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}}, + {?eh,cth,{'_',init,['_',[]]}}, {?eh,tc_start,{ct_cth_empty_SUITE,init_per_suite}}, {?eh,cth,{'_',pre_init_per_suite,[ct_cth_empty_SUITE,'$proplist',[]]}}, {?eh,cth,{'_',post_init_per_suite,[ct_cth_empty_SUITE,'$proplist',{skip,"Test skip"},[]]}}, @@ -699,8 +722,8 @@ test_events(skip_pre_suite_cth) -> test_events(skip_post_suite_cth) -> [ {?eh,start_logging,{'DEF','RUNDIR'}}, - {?eh,cth,{'_',init,['_',[]]}}, {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}}, + {?eh,cth,{'_',init,['_',[]]}}, {?eh,tc_start,{ct_cth_empty_SUITE,init_per_suite}}, {?eh,cth,{'_',pre_init_per_suite,[ct_cth_empty_SUITE,'$proplist',[]]}}, @@ -724,8 +747,8 @@ test_events(recover_post_suite_cth) -> Suite = ct_cth_fail_per_suite_SUITE, [ {?eh,start_logging,'_'}, - {?eh,cth,{'_',init,['_',[]]}}, {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}}, + {?eh,cth,{'_',init,['_',[]]}}, {?eh,tc_start,{Suite,init_per_suite}}, {?eh,cth,{'_',pre_init_per_suite,[Suite,'$proplist','$proplist']}}, {?eh,cth,{'_',post_init_per_suite,[Suite,contains([tc_status]), @@ -753,8 +776,8 @@ test_events(recover_post_suite_cth) -> test_events(update_config_cth) -> [ {?eh,start_logging,{'DEF','RUNDIR'}}, - {?eh,cth,{'_',init,['_',[]]}}, {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}}, + {?eh,cth,{'_',init,['_',[]]}}, {?eh,tc_start,{ct_update_config_SUITE,init_per_suite}}, {?eh,cth,{'_',pre_init_per_suite, @@ -864,9 +887,9 @@ test_events(update_config_cth) -> test_events(state_update_cth) -> [ {?eh,start_logging,{'DEF','RUNDIR'}}, + {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}}, {?eh,cth,{'_',init,['_',[]]}}, {?eh,cth,{'_',init,['_',[]]}}, - {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}}, {?eh,tc_start,{'_',init_per_suite}}, {?eh,tc_done,{'_',end_per_suite,ok}}, @@ -902,8 +925,8 @@ test_events(state_update_cth) -> test_events(options_cth) -> [ {?eh,start_logging,{'DEF','RUNDIR'}}, - {?eh,cth,{empty_cth,init,['_',[test]]}}, {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}}, + {?eh,cth,{empty_cth,init,['_',[test]]}}, {?eh,tc_start,{ct_cth_empty_SUITE,init_per_suite}}, {?eh,cth,{empty_cth,pre_init_per_suite, [ct_cth_empty_SUITE,'$proplist',[test]]}}, @@ -929,10 +952,10 @@ test_events(options_cth) -> test_events(same_id_cth) -> [ {?eh,start_logging,{'DEF','RUNDIR'}}, + {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}}, {?eh,cth,{'_',id,[[]]}}, {?eh,cth,{'_',init,[same_id_cth,[]]}}, {?eh,cth,{'_',id,[[]]}}, - {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}}, {?eh,tc_start,{ct_cth_empty_SUITE,init_per_suite}}, {?eh,cth,{'_',pre_init_per_suite,[ct_cth_empty_SUITE,'$proplist',[]]}}, {negative, @@ -969,8 +992,8 @@ test_events(same_id_cth) -> test_events(fail_n_skip_with_minimal_cth) -> [{?eh,start_logging,{'DEF','RUNDIR'}}, - {?eh,cth,{'_',init,['_',[]]}}, {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}}, + {?eh,cth,{'_',init,['_',[]]}}, {?eh,tc_start,{'_',init_per_suite}}, {?eh,tc_done,{'_',end_per_suite,ok}}, diff --git a/lib/common_test/test/ct_hooks_SUITE_data/cth/tests/ct_scope_suite_crash_in_cth_SUITE.erl b/lib/common_test/test/ct_hooks_SUITE_data/cth/tests/ct_scope_suite_crash_in_cth_SUITE.erl new file mode 100644 index 0000000000..5aa6b0132d --- /dev/null +++ b/lib/common_test/test/ct_hooks_SUITE_data/cth/tests/ct_scope_suite_crash_in_cth_SUITE.erl @@ -0,0 +1,50 @@ +%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2010-2011. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+-module(ct_scope_suite_crash_in_cth_SUITE).
+
+-suite_defaults([{timetrap, {minutes, 10}}]).
+
+%% Note: This directive should only be used in test suites.
+-compile(export_all).
+
+-include("ct.hrl").
+
+%% Test server callback functions
+suite() ->
+ [{ct_hooks,[fail_pre_suite_cth]}].
+
+init_per_suite(Config) ->
+ Config.
+
+end_per_suite(_Config) ->
+ ok.
+
+init_per_testcase(_TestCase, Config) ->
+ Config.
+
+end_per_testcase(_TestCase, _Config) ->
+ ok.
+
+all() ->
+ [test_case].
+
+%% Test cases starts here.
+test_case(Config) when is_list(Config) ->
+ ok.
diff --git a/lib/common_test/test/ct_master_SUITE.erl b/lib/common_test/test/ct_master_SUITE.erl index e89b6f7de6..1471cc1e0c 100644 --- a/lib/common_test/test/ct_master_SUITE.erl +++ b/lib/common_test/test/ct_master_SUITE.erl @@ -119,8 +119,9 @@ ct_master_test(Config) when is_list(Config)-> Events = ct_test_support:get_events(ERPid, Config), ct_test_support:log_events(groups_suite_1, - reformat(Events, ?eh), - ?config(priv_dir, Config)), + reformat(Events, ?eh), + PrivDir, []), + find_events(NodeNames, [{tc_start,{master_SUITE,init_per_suite}}, {tc_start,{master_SUITE,first_testcase}}, {tc_start,{master_SUITE,second_testcase}}, @@ -174,7 +175,7 @@ make_spec(DataDir, FileName, NodeNames, Suites, Config)-> ct_test_support:write_testspec(N++Include++EH++C++S++LD++NS, FileName). -get_log_dir({win32,_},PrivDir, NodeName)-> +get_log_dir({win32,_}, _PrivDir, NodeName)-> case filelib:is_dir(?TEMP_DIR) of false -> file:make_dir(?TEMP_DIR); diff --git a/lib/common_test/test/ct_misc_1_SUITE.erl b/lib/common_test/test/ct_misc_1_SUITE.erl index a8bd2c2189..cb17af9ab5 100644 --- a/lib/common_test/test/ct_misc_1_SUITE.erl +++ b/lib/common_test/test/ct_misc_1_SUITE.erl @@ -111,7 +111,8 @@ beam_me_up(Config) when is_list(Config) -> ct_test_support:log_events(beam_me_up, reformat(Events, ?eh), - ?config(priv_dir, Config)), + ?config(priv_dir, Config), + Opts), TestEvents = events_to_check(beam_me_up, 1), ok = ct_test_support:verify_events(TestEvents, Events, Config). diff --git a/lib/common_test/test/ct_repeat_1_SUITE.erl b/lib/common_test/test/ct_repeat_1_SUITE.erl index e674315526..4e842bd6d6 100644 --- a/lib/common_test/test/ct_repeat_1_SUITE.erl +++ b/lib/common_test/test/ct_repeat_1_SUITE.erl @@ -159,7 +159,8 @@ execute(TestCase, SuiteName, Group, Config) -> ct_test_support:log_events(TestCase, reformat(Events, ?eh), - ?config(priv_dir, Config)), + ?config(priv_dir, Config), + Opts), TestEvents = events_to_check(TestCase), ok = ct_test_support:verify_events(TestEvents, Events, Config). @@ -561,7 +562,6 @@ test_events(repeat_cs_until_any_fail) -> {error, {{badmatch,2}, [{repeat_1_SUITE,tc_fail_1,1}, - {repeat_1_SUITE,tc_fail_1,1}, {test_server,my_apply,3}, {test_server,ts_tc,3}, {test_server,run_test_case_eval1,6}, diff --git a/lib/common_test/test/ct_sequence_1_SUITE.erl b/lib/common_test/test/ct_sequence_1_SUITE.erl index c7650b169c..5facf90656 100644 --- a/lib/common_test/test/ct_sequence_1_SUITE.erl +++ b/lib/common_test/test/ct_sequence_1_SUITE.erl @@ -132,7 +132,8 @@ execute(TestCase, SuiteName, Group, Config) -> ct_test_support:log_events(TestCase, reformat(Events, ?eh), - ?config(priv_dir, Config)), + ?config(priv_dir, Config), + Opts), TestEvents = events_to_check(TestCase), ok = ct_test_support:verify_events(TestEvents, Events, Config). diff --git a/lib/common_test/test/ct_skip_SUITE.erl b/lib/common_test/test/ct_skip_SUITE.erl index 62c5f10b7c..4ba4479208 100644 --- a/lib/common_test/test/ct_skip_SUITE.erl +++ b/lib/common_test/test/ct_skip_SUITE.erl @@ -99,8 +99,9 @@ auto_skip(Config) when is_list(Config) -> Events = ct_test_support:get_events(ERPid, Config), ct_test_support:log_events(auto_skip, - reformat(Events, ?eh), - ?config(priv_dir, Config)), + reformat(Events, ?eh), + ?config(priv_dir, Config), + Opts), TestEvents = events_to_check(auto_skip), ok = ct_test_support:verify_events(TestEvents, Events, Config). @@ -122,8 +123,9 @@ user_skip(Config) when is_list(Config) -> Events = ct_test_support:get_events(ERPid, Config), ct_test_support:log_events(user_skip, - reformat(Events, ?eh), - ?config(priv_dir, Config)), + reformat(Events, ?eh), + ?config(priv_dir, Config), + Opts), TestEvents = events_to_check(user_skip), ok = ct_test_support:verify_events(TestEvents, Events, Config). diff --git a/lib/common_test/test/ct_smoke_test_SUITE.erl b/lib/common_test/test/ct_smoke_test_SUITE.erl index c3d49a5afa..49b38361e2 100644 --- a/lib/common_test/test/ct_smoke_test_SUITE.erl +++ b/lib/common_test/test/ct_smoke_test_SUITE.erl @@ -175,8 +175,9 @@ dir1(Config) when is_list(Config) -> Events = ct_test_support:get_events(ERPid, Config), ct_test_support:log_events(dir1, - ct_test_support:reformat(Events, ?eh), - ?config(priv_dir, Config)), + ct_test_support:reformat(Events, ?eh), + ?config(priv_dir, Config), + Opts), TestEvents = events_to_check(dir1), ok = ct_test_support:verify_events(TestEvents, Events, Config). @@ -204,8 +205,9 @@ dir2(Config) when is_list(Config) -> Events = ct_test_support:get_events(ERPid, Config), ct_test_support:log_events(dir2, - ct_test_support:reformat(Events, ?eh), - ?config(priv_dir, Config)), + ct_test_support:reformat(Events, ?eh), + ?config(priv_dir, Config), + Opts), TestEvents = events_to_check(dir2), ok = ct_test_support:verify_events(TestEvents, Events, Config). @@ -234,8 +236,9 @@ dir1_2(Config) when is_list(Config) -> Events = ct_test_support:get_events(ERPid, Config), ct_test_support:log_events(dir1_2, - ct_test_support:reformat(Events, ?eh), - ?config(priv_dir, Config)), + ct_test_support:reformat(Events, ?eh), + ?config(priv_dir, Config), + Opts), TestEvents = events_to_check(dir1_2), ok = ct_test_support:verify_events(TestEvents, Events, Config). @@ -264,8 +267,8 @@ suite11(Config) when is_list(Config) -> Events = ct_test_support:get_events(ERPid, Config), ct_test_support:log_events(suite11, - ct_test_support:reformat(Events, ?eh), - ?config(priv_dir, Config)), + ct_test_support:reformat(Events, ?eh), + ?config(priv_dir, Config), Opts), TestEvents = events_to_check(suite11), ok = ct_test_support:verify_events(TestEvents, Events, Config). @@ -293,8 +296,8 @@ suite21(Config) when is_list(Config) -> Events = ct_test_support:get_events(ERPid, Config), ct_test_support:log_events(suite21, - ct_test_support:reformat(Events, ?eh), - ?config(priv_dir, Config)), + ct_test_support:reformat(Events, ?eh), + ?config(priv_dir, Config), Opts), TestEvents = events_to_check(suite21), ok = ct_test_support:verify_events(TestEvents, Events, Config). @@ -324,8 +327,8 @@ suite11_21(Config) when is_list(Config) -> Events = ct_test_support:get_events(ERPid, Config), ct_test_support:log_events(suite11_21, - ct_test_support:reformat(Events, ?eh), - ?config(priv_dir, Config)), + ct_test_support:reformat(Events, ?eh), + ?config(priv_dir, Config), Opts), TestEvents = events_to_check(suite11_21), ok = ct_test_support:verify_events(TestEvents, Events, Config). @@ -355,8 +358,8 @@ tc111(Config) when is_list(Config) -> Events = ct_test_support:get_events(ERPid, Config), ct_test_support:log_events(tc111, - ct_test_support:reformat(Events, ?eh), - ?config(priv_dir, Config)), + ct_test_support:reformat(Events, ?eh), + ?config(priv_dir, Config), Opts), TestEvents = events_to_check(tc111), ok = ct_test_support:verify_events(TestEvents, Events, Config). @@ -385,8 +388,8 @@ tc211(Config) when is_list(Config) -> Events = ct_test_support:get_events(ERPid, Config), ct_test_support:log_events(tc211, - ct_test_support:reformat(Events, ?eh), - ?config(priv_dir, Config)), + ct_test_support:reformat(Events, ?eh), + ?config(priv_dir, Config), Opts), TestEvents = events_to_check(tc211), ok = ct_test_support:verify_events(TestEvents, Events, Config). @@ -416,8 +419,8 @@ tc111_112(Config) when is_list(Config) -> Events = ct_test_support:get_events(ERPid, Config), ct_test_support:log_events(tc111_112, - ct_test_support:reformat(Events, ?eh), - ?config(priv_dir, Config)), + ct_test_support:reformat(Events, ?eh), + ?config(priv_dir, Config), Opts), TestEvents = events_to_check(tc111_112), ok = ct_test_support:verify_events(TestEvents, Events, Config). diff --git a/lib/common_test/test/ct_test_server_if_1_SUITE.erl b/lib/common_test/test/ct_test_server_if_1_SUITE.erl index 9d3e6a9e59..4471915e69 100644 --- a/lib/common_test/test/ct_test_server_if_1_SUITE.erl +++ b/lib/common_test/test/ct_test_server_if_1_SUITE.erl @@ -98,8 +98,9 @@ ts_if_1(Config) when is_list(Config) -> Events = ct_test_support:get_events(ERPid, Config), ct_test_support:log_events(ts_if_1, - reformat(Events, ?eh), - PrivDir), + reformat(Events, ?eh), + PrivDir, + Opts), TestEvents = events_to_check(ts_if_1), ok = ct_test_support:verify_events(TestEvents, Events, Config). diff --git a/lib/common_test/test/ct_test_support.erl b/lib/common_test/test/ct_test_support.erl index b4f1a0e71f..601d5315ce 100644 --- a/lib/common_test/test/ct_test_support.erl +++ b/lib/common_test/test/ct_test_support.erl @@ -32,7 +32,8 @@ run/2, run/4, get_opts/1, wait_for_ct_stop/1]). -export([handle_event/2, start_event_receiver/1, get_events/2, - verify_events/3, reformat/2, log_events/3]). + verify_events/3, reformat/2, log_events/4, + join_abs_dirs/2]). -include_lib("kernel/include/file.hrl"). @@ -63,7 +64,6 @@ init_per_suite(Config, Level) -> start_slave(Config,Level) -> [_,Host] = string:tokens(atom_to_list(node()), "@"), - test_server:format(0, "Trying to start ~s~n", ["ct@"++Host]), case slave:start(Host, ct, []) of {error,Reason} -> @@ -72,18 +72,19 @@ start_slave(Config,Level) -> test_server:format(0, "Node ~p started~n", [CTNode]), IsCover = test_server:is_cover(), if IsCover -> - cover:start(CTNode); - true-> - ok + cover:start(CTNode); + true-> + ok end, - DataDir = ?config(data_dir, Config), - PrivDir = ?config(priv_dir, Config), + + DataDir = proplists:get_value(data_dir, Config), + PrivDir = proplists:get_value(priv_dir, Config), %% PrivDir as well as directory of Test Server suites %% have to be in code path on Common Test node. [_ | Parts] = lists:reverse(filename:split(DataDir)), TSDir = filename:join(lists:reverse(Parts)), - AddPathDirs = case ?config(path_dirs, Config) of + AddPathDirs = case proplists:get_value(path_dirs, Config) of undefined -> []; Ds -> Ds end, @@ -110,8 +111,8 @@ start_slave(Config,Level) -> %%% end_per_suite/1 end_per_suite(Config) -> - CTNode = ?config(ct_node, Config), - PrivDir = ?config(priv_dir, Config), + CTNode = proplists:get_value(ct_node, Config), + PrivDir = proplists:get_value(priv_dir, Config), true = rpc:call(CTNode, code, del_path, [filename:join(PrivDir,"")]), cover:stop(CTNode), slave:stop(CTNode), @@ -121,7 +122,9 @@ end_per_suite(Config) -> %%% init_per_testcase/2 init_per_testcase(_TestCase, Config) -> - {_,{_,LogDir}} = lists:keysearch(logdir, 1, get_opts(Config)), + Opts = get_opts(Config), + NetDir = proplists:get_value(net_dir, Opts), + LogDir = join_abs_dirs(NetDir, proplists:get_value(logdir, Opts)), case lists:keysearch(master, 1, Config) of false-> test_server:format("See Common Test logs here:\n\n" @@ -139,7 +142,7 @@ init_per_testcase(_TestCase, Config) -> %%% end_per_testcase/2 end_per_testcase(_TestCase, Config) -> - CTNode = ?config(ct_node, Config), + CTNode = proplists:get_value(ct_node, Config), case wait_for_ct_stop(CTNode) of %% Common test was not stopped to we restart node. false -> @@ -169,7 +172,7 @@ write_testspec(TestSpec, TSFile) -> %%% get_opts(Config) -> - PrivDir = ?config(priv_dir, Config), + PrivDir = proplists:get_value(priv_dir, Config), TempDir = case os:getenv("TMP") of false -> case os:getenv("TEMP") of @@ -195,20 +198,48 @@ get_opts(Config) -> _ -> TempDir end, - InitOpts = ?config(ct_opts, Config), - [{logdir,LogDir} | InitOpts]. + + %% Copy test variables to app environment on new node + CtTestVars = + case init:get_argument(ct_test_vars) of + {ok,[Vars]} -> + [begin {ok,Ts,_} = erl_scan:string(Str++"."), + {ok,Expr} = erl_parse:parse_term(Ts), + Expr + end || Str <- Vars]; + _ -> + [] + end, + %% test_server:format("Test variables added to Config: ~p\n\n", + %% [CtTestVars]), + InitOpts = + case proplists:get_value(ct_opts, Config) of + undefined -> []; + CtOpts -> CtOpts + end, + [{logdir,LogDir} | InitOpts ++ CtTestVars]. %%%----------------------------------------------------------------- %%% run(Opts, Config) -> - CTNode = ?config(ct_node, Config), - Level = ?config(trace_level, Config), + CTNode = proplists:get_value(ct_node, Config), + Level = proplists:get_value(trace_level, Config), %% use ct interface test_server:format(Level, "~n[RUN #1] Calling ct:run_test(~p) on ~p~n", [Opts, CTNode]), Result1 = rpc:call(CTNode, ct, run_test, [Opts]), + case rpc:call(CTNode, erlang, whereis, [ct_util_server]) of + undefined -> + ok; + _ -> + test_server:format(Level, + "ct_util_server not stopped on ~p yet, waiting 5 s...~n", + [CTNode]), + timer:sleep(5000), + undefined = rpc:call(CTNode, erlang, whereis, [ct_util_server]) + end, %% use run_test interface (simulated) test_server:format(Level, "Saving start opts on ~p: ~p~n", [CTNode,Opts]), rpc:call(CTNode, application, set_env, [common_test, run_test_start_opts, Opts]), @@ -224,8 +255,8 @@ run(Opts, Config) -> end. run(M, F, A, Config) -> - CTNode = ?config(ct_node, Config), - Level = ?config(trace_level, Config), + CTNode = proplists:get_value(ct_node, Config), + Level = proplists:get_value(trace_level, Config), test_server:format(Level, "~nCalling ~w:~w(~p) on ~p~n", [M, F, A, CTNode]), rpc:call(CTNode, M, F, A). @@ -261,11 +292,11 @@ handle_event(EH, Event) -> ok. start_event_receiver(Config) -> - CTNode = ?config(ct_node, Config), + CTNode = proplists:get_value(ct_node, Config), spawn_link(CTNode, fun() -> er() end). get_events(_, Config) -> - CTNode = ?config(ct_node, Config), + CTNode = proplists:get_value(ct_node, Config), {event_receiver,CTNode} ! {self(),get_events}, Events = receive {event_receiver,Evs} -> Evs end, {event_receiver,CTNode} ! stop, @@ -288,7 +319,7 @@ er_loop(Evs) -> end. verify_events(TEvs, Evs, Config) -> - Node = ?config(ct_node, Config), + Node = proplists:get_value(ct_node, Config), case catch verify_events1(TEvs, Evs, Node, Config) of {'EXIT',Reason} -> Reason; @@ -349,10 +380,15 @@ locate(TEvs, Node, Evs, Config) when is_list(TEvs) -> data={M,{init_per_group,GroupName,Props}}}}, {TEH,#event{name=tc_done, node=Node, - data={M,{init_per_group,GroupName,Props},R}}} | Evs1] -> - test_server:format("Found ~p!", [InitStart]), - test_server:format("Found ~p!", [InitDone]), - verify_events1(TEvs1, Evs1, Node, Config); + data={M,{init_per_group,GroupName,Props},Res}}} | Evs1] -> + case result_match(R, Res) of + false -> + nomatch; + true -> + test_server:format("Found ~p!", [InitStart]), + test_server:format("Found ~p!", [InitDone]), + verify_events1(TEvs1, Evs1, Node, Config) + end; _ -> nomatch end; @@ -384,9 +420,11 @@ locate({parallel,TEvs}, Node, Evs, Config) -> EvProps},EvR}}}) when TEH == EH, EvNode == Node, EvM == M, EvGroupName == GroupName, - EvProps == Props, - EvR == R -> - false; + EvProps == Props -> + case result_match(R, EvR) of + true -> false; + false -> true + end; ({EH,#event{name=stop_logging, node=EvNode,data=_}}) when EH == TEH, EvNode == Node -> @@ -466,7 +504,7 @@ locate({parallel,TEvs}, Node, Evs, Config) -> node=EvNode, data={Mod,Func,Result}}} <- Done, EH == TEH, EvNode == Node, Mod == M, - Func == F, Result == R] of + Func == F, result_match(R, Result)] of [TcDone|_] -> test_server:format("Found ~p!", [TEv]), {lists:delete(TcDone, Done),RemEvs,RemSize}; @@ -509,8 +547,13 @@ locate({parallel,TEvs}, Node, Evs, Config) -> data={Mod,{end_per_group, EvGName,EvProps},Res}}}) when EH == TEH, EvNode == Node, Mod == M, - EvGName == GroupName, EvProps == Props, Res == R -> - false; + EvGName == GroupName, EvProps == Props -> + case result_match(R, Res) of + true -> + false; + false -> + true + end; ({EH,#event{name=stop_logging, node=EvNode,data=_}}) when EH == TEH, EvNode == Node -> @@ -603,23 +646,29 @@ locate({shuffle,TEvs}, Node, Evs, Config) -> data={M,{init_per_group,GroupName,EvProps}}}}, {TEH,#event{name=tc_done, node=Node, - data={M,{init_per_group,GroupName,EvProps},R}}} | Es] -> - case proplists:get_value(shuffle, Props) of - '_' -> - case proplists:get_value(shuffle, EvProps) of - false -> - exit({no_shuffle_prop_found,{M,init_per_group, - GroupName,EvProps}}); + data={M,{init_per_group,GroupName,EvProps},Res}}} | Es] -> + case result_match(R, Res) of + true -> + case proplists:get_value(shuffle, Props) of + '_' -> + case proplists:get_value(shuffle, EvProps) of + false -> + exit({no_shuffle_prop_found, + {M,init_per_group, + GroupName,EvProps}}); + _ -> + PropsCmp = proplists:delete(shuffle, EvProps), + PropsCmp = proplists:delete(shuffle, Props) + end; _ -> - PropsCmp = proplists:delete(shuffle, EvProps), - PropsCmp = proplists:delete(shuffle, Props) - end; - _ -> - Props = EvProps - end, - test_server:format("Found ~p!", [InitStart]), - test_server:format("Found ~p!", [InitDone]), - {TEs,Es}; + Props = EvProps + end, + test_server:format("Found ~p!", [InitStart]), + test_server:format("Found ~p!", [InitDone]), + {TEs,Es}; + false -> + nomatch + end; _ -> nomatch end; @@ -670,7 +719,7 @@ locate({shuffle,TEvs}, Node, Evs, Config) -> node=EvNode, data={Mod,Func,Result}}} <- Done, EH == TEH, EvNode == Node, Mod == M, - Func == F, Result == R] of + Func == F, result_match(R, Result)] of [TcDone|_] -> test_server:format("Found ~p!", [TEv]), {lists:delete(TcDone, Done),RemEvs,RemSize}; @@ -726,8 +775,13 @@ locate({shuffle,TEvs}, Node, Evs, Config) -> data={Mod,{end_per_group, EvGName,_},Res}}}) when EH == TEH, EvNode == Node, Mod == M, - EvGName == GroupName, Res == R -> - false; + EvGName == GroupName -> + case result_match(R, Res) of + true -> + false; + false -> + true + end; ({EH,#event{name=stop_logging, node=EvNode,data=_}}) when EH == TEH, EvNode == Node -> @@ -864,25 +918,34 @@ locate({TEH,Name,{'DEF','STOP_TIME'}}, Node, [Ev|Evs], Config) -> nomatch end; -%% to match variable data as a result of a failed test case -locate({TEH,tc_done,{Mod,Func,{failed,{error,{Slogan,'_'}}}}}, Node, [Ev|Evs], Config) -> +%% to match variable data as a result of an aborted test case +locate({TEH,tc_done,{undefined,undefined,{testcase_aborted, + {abort_current_testcase,Func},'_'}}}, + Node, [Ev|Evs], Config) -> case Ev of - {TEH,#event{name=tc_done, node=Node, - data={Mod,Func,{failed,{error,{Slogan,_}}}}}} -> + {TEH,#event{name=tc_done, node=Node, + data={undefined,undefined, + {testcase_aborted,{abort_current_testcase,Func},_}}}} -> {Config,Evs}; _ -> nomatch end; -%% to match variable data as a result of an aborted test case -locate({TEH,tc_done,{undefined,undefined,{testcase_aborted, - {abort_current_testcase,Func},'_'}}}, - Node, [Ev|Evs], Config) -> +%% to match variable data as a result of a failed test case +locate({TEH,tc_done,{Mod,Func,R={SkipOrFail,{_ErrInd,ErrInfo}}}}, + Node, [Ev|Evs], Config) when ((SkipOrFail == skipped) or + (SkipOrFail == failed)) and + ((size(ErrInfo) == 2) or + (size(ErrInfo) == 3)) -> case Ev of {TEH,#event{name=tc_done, node=Node, - data={undefined,undefined, - {testcase_aborted,{abort_current_testcase,Func},_}}}} -> - {Config,Evs}; + data={Mod,Func,Result}}} -> + case result_match(R, Result) of + true -> + {Config,Evs}; + false -> + nomatch + end; _ -> nomatch end; @@ -931,14 +994,27 @@ match_data(Tuple1,Tuple2) when is_tuple(Tuple1),is_tuple(Tuple2) -> match_data([],[]) -> match. -log_events(TC, Events, PrivDir) -> - LogFile = filename:join(PrivDir, atom_to_list(TC)++".events"), +result_match({SkipOrFail,{ErrorInd,{Why,'_'}}}, + {SkipOrFail,{ErrorInd,{Why,_Stack}}}) -> + true; +result_match({SkipOrFail,{ErrorInd,{EMod,EFunc,{Why,'_'}}}}, + {SkipOrFail,{ErrorInd,{EMod,EFunc,{Why,_Stack}}}}) -> + true; +result_match(Result, Result) -> + true; +result_match(_, _) -> + false. + +log_events(TC, Events, EvLogDir, Opts) -> + LogFile = filename:join(EvLogDir, atom_to_list(TC)++".events"), {ok,Dev} = file:open(LogFile, [write]), io:format(Dev, "[~n", []), log_events1(Events, Dev, " "), file:close(Dev), + FullLogFile = join_abs_dirs(proplists:get_value(net_dir, Opts), + LogFile), io:format("Events written to logfile: <a href=\"file://~s\">~s</a>~n", - [LogFile,LogFile]), + [FullLogFile,FullLogFile]), io:format(user, "Events written to logfile: ~p~n", [LogFile]). log_events1(Evs, Dev, "") -> @@ -1024,13 +1100,25 @@ reformat([], _EH) -> %%%----------------------------------------------------------------- %%% MISC HELP FUNCTIONS +join_abs_dirs(undefined, Dir2) -> + Dir2; +join_abs_dirs(Dir1, Dir2) -> + case filename:pathtype(Dir2) of + relative -> + filename:join(Dir1, Dir2); + _ -> + [_Abs|Parts] = filename:split(Dir2), + filename:join(Dir1, filename:join(Parts)) + end. + create_tmp_logdir(Tmp) -> LogDir = filename:join(Tmp,"ct"), file:make_dir(LogDir), LogDir. delete_old_logs({win32,_}, Config) -> - case {?config(priv_dir, Config),?config(logdir, get_opts(Config))} of + case {proplists:get_value(priv_dir, Config), + proplists:get_value(logdir, get_opts(Config))} of {LogDir,LogDir} -> ignore; {_,LogDir} -> % using tmp for logs @@ -1042,7 +1130,8 @@ delete_old_logs(_, Config) -> false -> ignore; _ -> - catch delete_dirs(?config(logdir, get_opts(Config))) + catch delete_dirs(proplists:get_value(logdir, + get_opts(Config))) end. delete_dirs(LogDir) -> diff --git a/lib/common_test/test/ct_testspec_1_SUITE.erl b/lib/common_test/test/ct_testspec_1_SUITE.erl index 616c2db869..b6dcf63fdf 100644 --- a/lib/common_test/test/ct_testspec_1_SUITE.erl +++ b/lib/common_test/test/ct_testspec_1_SUITE.erl @@ -612,6 +612,12 @@ setup_and_execute(TCName, TestSpec, Config) -> false -> [{spec,SpecFile},{label,TCName}] end, {Opts,ERPid} = setup(TestTerms, Config), + + FullSpecFile = ct_test_support:join_abs_dirs(?config(net_dir, Opts), + SpecFile), + io:format("~nTest spec created here~n~n<a href=\"file://~s\">~s</a>~n", + [FullSpecFile,FullSpecFile]), + ok = ct_test_support:run(Opts, Config), TestSpec1 = [{logdir,proplists:get_value(logdir,Opts)}, {label,proplists:get_value(label,TestTerms)} | TestSpec], @@ -620,7 +626,8 @@ setup_and_execute(TCName, TestSpec, Config) -> ct_test_support:log_events(TCName, reformat(Events, ?eh), - ?config(priv_dir, Config)), + ?config(priv_dir, Config), + Opts), TestEvents = events_to_check(TCName), ok = ct_test_support:verify_events(TestEvents, Events, Config). @@ -631,8 +638,6 @@ create_spec_file(SpecDir, TCName, TestSpec) -> {ok,Dev} = file:open(FileName, [write]), [io:format(Dev, "~p.~n", [Term]) || Term <- TestSpec], file:close(Dev), - io:format("~nTest spec created here~n~n<a href=\"file://~s\">~s</a>~n", - [FileName,FileName]), FileName. setup(Test, Config) when is_tuple(Test) -> @@ -791,7 +796,9 @@ test_events(skip_group) -> {?eh,tc_done,{groups_11_SUITE,{end_per_group,test_group_1a,[]},'_'}}, {?eh,tc_user_skip, {groups_11_SUITE,{group,test_group_1b},"SKIPPED!"}}, - {?eh,test_stats,{2,0,{1,0}}}, + {?eh,tc_user_skip, {groups_11_SUITE,{group,test_group_2},"SKIPPED!"}}, + %%! But not test_group_7 since it's a sub-group! + {?eh,test_stats,{2,0,{2,0}}}, {negative,{?eh,tc_user_skip,'_'},{?eh,stop_logging,'_'}} ]; @@ -1188,10 +1195,9 @@ test_events(sub_skipped_by_top) -> {?eh,tc_start,{groups_12_SUITE,init_per_suite}}, {?eh,tc_user_skip,{groups_12_SUITE,{group,test_group_4},"SKIPPED!"}}, + {?eh,tc_user_skip,{groups_12_SUITE,{group,test_group_4},"SKIPPED!"}}, - {negative, - {?eh,tc_user_skip,{groups_12_SUITE,{group,test_group_4},"SKIPPED!"}}, - {?eh,tc_done,{groups_12_SUITE,end_per_suite,'_'}}}, + {?eh,tc_done,{groups_12_SUITE,end_per_suite,'_'}}, {negative,{?eh,tc_start,'_'},{?eh,stop_logging,'_'}} ]; diff --git a/lib/erl_interface/src/legacy/erl_timeout.c b/lib/erl_interface/src/legacy/erl_timeout.c index d9560eebc8..146a106e7c 100644 --- a/lib/erl_interface/src/legacy/erl_timeout.c +++ b/lib/erl_interface/src/legacy/erl_timeout.c @@ -43,6 +43,7 @@ # endif #endif +#include "erl_interface.h" #include "erl_timeout.h" typedef struct jmp_s { diff --git a/lib/eunit/src/eunit.erl b/lib/eunit/src/eunit.erl index 4a86a108cf..da35c5c2ec 100644 --- a/lib/eunit/src/eunit.erl +++ b/lib/eunit/src/eunit.erl @@ -157,6 +157,7 @@ test_run(Reference, Listeners) -> receive {done, Reference} -> cast(Listeners, {stop, Reference, self()}), + wait_until_listeners_have_terminated(Listeners), receive {result, Reference, Result} -> Result @@ -169,6 +170,15 @@ cast([P | Ps], Msg) -> cast([], _Msg) -> ok. +wait_until_listeners_have_terminated([P | Ps]) -> + MRef = erlang:monitor(process, P), + receive + {'DOWN', MRef, process, P, _} -> + wait_until_listeners_have_terminated(Ps) + end; +wait_until_listeners_have_terminated([]) -> + ok. + %% TODO: functions that run tests on a given node, not a given server %% TODO: maybe some functions could check for a globally registered server? %% TODO: some synchronous but completely quiet interface function diff --git a/lib/eunit/src/eunit_surefire.erl b/lib/eunit/src/eunit_surefire.erl index f289cd724a..dfb08c90b2 100644 --- a/lib/eunit/src/eunit_surefire.erl +++ b/lib/eunit/src/eunit_surefire.erl @@ -100,16 +100,10 @@ terminate({ok, _Data}, St) -> XmlDir = St#state.xmldir, write_report(TestSuite, XmlDir), ok; -terminate({error, Reason}, _St) -> - io:fwrite("Internal error: ~P.\n", [Reason, 25]), - sync_end(error). - -sync_end(Result) -> - receive - {stop, Reference, ReplyTo} -> - ReplyTo ! {result, Reference, Result}, - ok - end. +terminate({error, _Reason}, _St) -> + %% Don't report any errors here, since eunit_tty takes care of that. + %% Just terminate. + ok. handle_begin(group, Data, St) -> NewId = proplists:get_value(id, Data), diff --git a/lib/kernel/src/inet_res.erl b/lib/kernel/src/inet_res.erl index de0f23bf24..93563c6011 100644 --- a/lib/kernel/src/inet_res.erl +++ b/lib/kernel/src/inet_res.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 1997-2010. All Rights Reserved. +%% Copyright Ericsson AB 1997-2011. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -539,27 +539,41 @@ udp_send(#sock{inet=I}, {A,B,C,D}=IP, Port, Buffer) when ?ip(A,B,C,D), ?port(Port) -> gen_udp:send(I, IP, Port, Buffer). -udp_recv(#sock{inet6=I}, {A,B,C,D,E,F,G,H}=IP, Port, Timeout) +udp_recv(#sock{inet6=I}, {A,B,C,D,E,F,G,H}=IP, Port, Timeout, Decode) when ?ip6(A,B,C,D,E,F,G,H), ?port(Port) -> - do_udp_recv(fun(T) -> gen_udp:recv(I, 0, T) end, IP, Port, Timeout); -udp_recv(#sock{inet=I}, {A,B,C,D}=IP, Port, Timeout) + do_udp_recv(I, IP, Port, Timeout, Decode, erlang:now(), Timeout); +udp_recv(#sock{inet=I}, {A,B,C,D}=IP, Port, Timeout, Decode) when ?ip(A,B,C,D), ?port(Port) -> - do_udp_recv(fun(T) -> gen_udp:recv(I, 0, T) end, IP, Port, Timeout). - -do_udp_recv(Recv, IP, Port, Timeout) -> - do_udp_recv(Recv, IP, Port, Timeout, - if Timeout =/= 0 -> erlang:now(); true -> undefined end). - -do_udp_recv(Recv, IP, Port, Timeout, Then) -> - case Recv(Timeout) of - {ok,{IP,Port,Answer}} -> - {ok,Answer,erlang:max(0, Timeout - now_ms(erlang:now(), Then))}; - {ok,_} when Timeout =:= 0 -> - {error,timeout}; - {ok,_} -> - Now = erlang:now(), - T = erlang:max(0, Timeout - now_ms(Now, Then)), - do_udp_recv(Recv, IP, Port, T, Now); + do_udp_recv(I, IP, Port, Timeout, Decode, erlang:now(), Timeout). + +do_udp_recv(_I, _IP, _Port, 0, _Decode, _Start, _T) -> + timeout; +do_udp_recv(I, IP, Port, Timeout, Decode, Start, T) -> + case gen_udp:recv(I, 0, T) of + {ok,Reply} -> + case Decode(Reply) of + false when T =:= 0 -> + %% This is a compromize between the hard way i.e + %% in the clause below if NewT becomes 0 bailout + %% immediately and risk that the right reply lies + %% ahead after some bad id replies, and the + %% forgiving way i.e go on with Timeout 0 until + %% the right reply comes or no reply (timeout) + %% which opens for a DOS attack by a malicious + %% DNS server flooding with bad id replies causing + %% an infinite loop here. + %% + %% Timeout is used as a sanity limit counter + %% just to put an end to the loop. + NewTimeout = erlang:max(0, Timeout - 50), + do_udp_recv(I, IP, Port, NewTimeout, Decode, Start, T); + false -> + Now = erlang:now(), + NewT = erlang:max(0, Timeout - now_ms(Now, Start)), + do_udp_recv(I, IP, Port, Timeout, Decode, Start, NewT); + Result -> + Result + end; Error -> Error end. @@ -580,6 +594,17 @@ udp_close(#sock{inet=I,inet6=I6}) -> %% end %% end %% +%% But that man page also says dig always use num_servers = 1. +%% +%% Our man page says: timeout/retry, then double for next retry, i.e +%% for i = 0 to retry - 1 +%% foreach nameserver +%% send query +%% wait((time * (2**i)) / retry) +%% end +%% end +%% +%% And that is what the code seems to do, now fixed, hopefully... do_query(_Q, [], _Timer) -> {error,nxdomain}; @@ -589,19 +614,16 @@ do_query(#q{options=#options{retry=Retry}}=Q, NSs, Timer) -> query_retries(_Q, _NSs, _Timer, Retry, Retry, S) -> udp_close(S), {error,timeout}; +query_retries(_Q, [], _Timer, _Retry, _I, S) -> + udp_close(S), + {error,timeout}; query_retries(Q, NSs, Timer, Retry, I, S0) -> - Num = length(NSs), - if Num =:= 0 -> - udp_close(S0), - {error,timeout}; - true -> - case query_nss(Q, NSs, Timer, Retry, I, S0, []) of - {S,{noanswer,ErrNSs}} -> %% remove unreachable nameservers - query_retries(Q, NSs--ErrNSs, Timer, Retry, I+1, S); - {S,Result} -> - udp_close(S), - Result - end + case query_nss(Q, NSs, Timer, Retry, I, S0, []) of + {S,{noanswer,ErrNSs}} -> %% remove unreachable nameservers + query_retries(Q, NSs--ErrNSs, Timer, Retry, I+1, S); + {S,Result} -> + udp_close(S), + Result end. query_nss(_Q, [], _Timer, _Retry, _I, S, ErrNSs) -> @@ -611,13 +633,13 @@ query_nss(#q{edns=undefined}=Q, NSs, Timer, Retry, I, S, ErrNSs) -> query_nss(Q, NSs, Timer, Retry, I, S, ErrNSs) -> query_nss_edns(Q, NSs, Timer, Retry, I, S, ErrNSs). -query_nss_edns(#q{options=#options{udp_payload_size=PSz}=Options, - edns={Id,Buffer}}=Q, - [{IP,Port}=NS|NSs]=NSs0, Timer, Retry, I, S0, ErrNSs) -> - {S,Res}=Reply = query_ns(S0, Id, Buffer, IP, Port, Timer, - Retry, I, Options, PSz), +query_nss_edns( + #q{options=#options{udp_payload_size=PSz}=Options,edns={Id,Buffer}}=Q, + [{IP,Port}=NS|NSs]=NSs0, Timer, Retry, I, S0, ErrNSs) -> + {S,Res}=Reply = + query_ns(S0, Id, Buffer, IP, Port, Timer, Retry, I, Options, PSz), case Res of - timeout -> {S,{error,timeout}}; + timeout -> {S,{error,timeout}}; % Bailout timeout {ok,_} -> Reply; {error,{nxdomain,_}} -> Reply; {error,{E,_}} when E =:= qfmterror; E =:= notimp; E =:= servfail; @@ -629,17 +651,19 @@ query_nss_edns(#q{options=#options{udp_payload_size=PSz}=Options, query_nss(Q, NSs, Timer, Retry, I, S, ErrNSs) end. -query_nss_dns(#q{dns=Qdns}=Q0, [{IP,Port}=NS|NSs], - Timer, Retry, I, S0, ErrNSs) -> +query_nss_dns( + #q{dns=Qdns}=Q0, + [{IP,Port}=NS|NSs], Timer, Retry, I, S0, ErrNSs) -> #q{options=Options,dns={Id,Buffer}}=Q = if is_function(Qdns, 0) -> Q0#q{dns=Qdns()}; true -> Q0 end, - {S,Res}=Reply = query_ns(S0, Id, Buffer, IP, Port, Timer, - Retry, I, Options, ?PACKETSZ), + {S,Res}=Reply = + query_ns( + S0, Id, Buffer, IP, Port, Timer, Retry, I, Options, ?PACKETSZ), case Res of - timeout -> {S,{error,timeout}}; + timeout -> {S,{error,timeout}}; % Bailout timeout {ok,_} -> Reply; {error,{E,_}} when E =:= nxdomain; E =:= qfmterror -> Reply; {error,E} when E =:= fmt; E =:= enetunreach; E =:= econnrefused -> @@ -653,48 +677,66 @@ query_ns(S0, Id, Buffer, IP, Port, Timer, Retry, I, PSz) -> case UseVC orelse iolist_size(Buffer) > PSz of true -> - {S0,query_tcp(Tm, Id, Buffer, IP, Port, Timer, Verbose)}; + TcpTimeout = inet:timeout(Tm*5, Timer), + {S0,query_tcp(TcpTimeout, Id, Buffer, IP, Port, Verbose)}; false -> case udp_open(S0, IP) of {ok,S} -> - {S,case query_udp(S, Id, Buffer, IP, Port, Timer, - Retry, I, Tm, Verbose) of - {ok,#dns_rec{header=H}} when H#dns_header.tc -> - query_tcp(Tm, Id, Buffer, - IP, Port, Timer, Verbose); - Reply -> Reply - end}; + Timeout = + inet:timeout( (Tm * (1 bsl I)) div Retry, Timer), + {S, + case query_udp( + S, Id, Buffer, IP, Port, Timeout, Verbose) of + {ok,#dns_rec{header=H}} when H#dns_header.tc -> + TcpTimeout = inet:timeout(Tm*5, Timer), + query_tcp( + TcpTimeout, Id, Buffer, IP, Port, Verbose); + Reply -> Reply + end}; Error -> {S0,Error} end end. -query_udp(S, Id, Buffer, IP, Port, Timer, Retry, I, Tm, Verbose) -> - Timeout = inet:timeout( (Tm * (1 bsl I)) div Retry, Timer), +query_udp(_S, _Id, _Buffer, _IP, _Port, 0, Verbose) -> + timeout; +query_udp(S, Id, Buffer, IP, Port, Timeout, Verbose) -> ?verbose(Verbose, "Try UDP server : ~p:~p (timeout=~w)\n", - [IP, Port, Timeout]), - udp_connect(S, IP, Port), - udp_send(S, IP, Port, Buffer), - query_udp_recv(S, IP, Port, Id, Timeout, Verbose). - -query_udp_recv(S, IP, Port, Id, Timeout, Verbose) -> - case udp_recv(S, IP, Port, Timeout) of - {ok,Answer,T} -> - case decode_answer(Answer, Id, Verbose) of - {error, badid} -> - query_udp_recv(S, IP, Port, Id, T, Verbose); - Reply -> Reply + [IP,Port,Timeout]), + case + case udp_connect(S, IP, Port) of + ok -> + udp_send(S, IP, Port, Buffer); + E1 -> + E1 end of + ok -> + Decode = + fun ({RecIP,RecPort,Answer}) + when RecIP =:= IP, RecPort =:= Port -> + case decode_answer(Answer, Id, Verbose) of + {error,badid} -> + false; + Reply -> + Reply + end; + ({_,_,_}) -> + false + end, + case udp_recv(S, IP, Port, Timeout, Decode) of + {ok,_}=Result -> + Result; + E2 -> + ?verbose(Verbose, "UDP server error: ~p\n", [E2]), + E2 end; - {error, timeout} when Timeout =:= 0 -> - ?verbose(Verbose, "UDP server timeout\n", []), - timeout; - Error -> - ?verbose(Verbose, "UDP server error: ~p\n", [Error]), - Error + E3 -> + ?verbose(Verbose, "UDP send failed: ~p\n", [E3]), + {error,econnrefused} end. -query_tcp(Tm, Id, Buffer, IP, Port, Timer, Verbose) -> - Timeout = inet:timeout(Tm*5, Timer), +query_tcp(0, _Id, _Buffer, _IP, _Port, Verbose) -> + timeout; +query_tcp(Timeout, Id, Buffer, IP, Port, Verbose) -> ?verbose(Verbose, "Try TCP server : ~p:~p (timeout=~w)\n", [IP, Port, Timeout]), Family = case IP of @@ -716,19 +758,10 @@ query_tcp(Tm, Id, Buffer, IP, Port, Timer, Verbose) -> end; Error -> gen_tcp:close(S), - case Error of - {error, timeout} when Timeout =:= 0 -> - ?verbose(Verbose, "TCP server recv timeout\n", []), - timeout; - _ -> - ?verbose(Verbose, "TCP server recv error: ~p\n", - [Error]), - Error - end + ?verbose(Verbose, "TCP server recv error: ~p\n", + [Error]), + Error end; - {error, timeout} when Timeout =:= 0 -> - ?verbose(Verbose, "TCP server connect timeout\n", []), - timeout; Error -> ?verbose(Verbose, "TCP server error: ~p\n", [Error]), Error diff --git a/lib/kernel/test/file_SUITE.erl b/lib/kernel/test/file_SUITE.erl index 8078c7d021..2f73394c4e 100644 --- a/lib/kernel/test/file_SUITE.erl +++ b/lib/kernel/test/file_SUITE.erl @@ -2055,6 +2055,10 @@ try_read_file_list(Fd) -> ?line Title = "Real Programmers Don't Use PASCAL</TITLE>\n", ?line Title = io:get_line(Fd, ''), + %% Seek past the end of the file. + + ?line {ok, _} = ?FILE_MODULE:position(Fd, 25000), + %% Done. ?line ?FILE_MODULE:close(Fd), diff --git a/lib/kernel/test/inet_res_SUITE.erl b/lib/kernel/test/inet_res_SUITE.erl index 5fc8df475d..6064a9b2d9 100644 --- a/lib/kernel/test/inet_res_SUITE.erl +++ b/lib/kernel/test/inet_res_SUITE.erl @@ -27,7 +27,8 @@ -export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1, init_per_group/2,end_per_group/2, init_per_testcase/2, end_per_testcase/2]). --export([basic/1, resolve/1, edns0/1, txt_record/1, files_monitor/1]). +-export([basic/1, resolve/1, edns0/1, txt_record/1, files_monitor/1, + last_ms_answer/1]). -export([ gethostbyaddr/0, gethostbyaddr/1, gethostbyaddr_v6/0, gethostbyaddr_v6/1, @@ -45,6 +46,7 @@ suite() -> [{ct_hooks,[ts_install_cth]}]. all() -> [basic, resolve, edns0, txt_record, files_monitor, + last_ms_answer, gethostbyaddr, gethostbyaddr_v6, gethostbyname, gethostbyname_v6, getaddr, getaddr_v6, ipv4_to_ipv6, host_and_addr]. @@ -64,16 +66,15 @@ init_per_group(_GroupName, Config) -> end_per_group(_GroupName, Config) -> Config. -zone_dir(basic) -> - otptest; -zone_dir(resolve) -> - otptest; -zone_dir(edns0) -> - otptest; -zone_dir(files_monitor) -> - otptest; -zone_dir(_) -> - undefined. +zone_dir(TC) -> + case TC of + basic -> otptest; + resolve -> otptest; + edns0 -> otptest; + files_monitor -> otptest; + last_ms_answer -> otptest; + _ -> undefined + end. init_per_testcase(Func, Config) -> PrivDir = ?config(priv_dir, Config), @@ -184,6 +185,88 @@ ns_printlog(Fname) -> ok end. +%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Behaviour modifying nameserver proxy + +proxy_start(TC, {NS,P}) -> + Tag = make_ref(), + Parent = self(), + Pid = + spawn_link( + fun () -> + try proxy_start(TC, NS, P, Parent, Tag) + catch C:X -> + io:format( + "~w: ~w:~p ~p~n", + [self(),C,X,erlang:get_stacktrace()]) + end + end), + receive {started,Tag,Port} -> + ProxyNS = {{127,0,0,1},Port}, + {proxy,Pid,Tag,ProxyNS} + end. + +proxy_start(TC, NS, P, Parent, Tag) -> + {ok,Outbound} = gen_udp:open(0, [binary]), + ok = gen_udp:connect(Outbound, NS, P), + {ok,Inbound} = gen_udp:open(0, [binary]), + {ok,Port} = inet:port(Inbound), + Parent ! {started,Tag,Port}, + proxy(TC, Outbound, NS, P, Inbound). + + +%% To provoke the last_ms_answer bug (OTP-9221) the proxy +%% * Relays the query to the right nameserver +%% * Intercepts the reply but holds it until the timer that +%% was started when receiving the query fires. +%% * Repeats the reply with incorrect query ID a number of +%% times with a short interval. +%% * Sends the correct reply, to give a correct test result +%% after bug correction. +%% +%% The repetition of an incorrect answer with tight interval will keep +%% inet_res in an inner loop in the code that decrements the remaining +%% time until it hits 0 which triggers a crash, if the outer timeout +%% parameter to inet_res:resolve is so short that it runs out during +%% these repetitions. +proxy(last_ms_answer, Outbound, NS, P, Inbound) -> + receive + {udp,Inbound,SrcIP,SrcPort,Data} -> + Time = + inet_db:res_option(timeout) div inet_db:res_option(retry), + Tag = erlang:make_ref(), + erlang:send_after(Time - 10, self(), {time,Tag}), + ok = gen_udp:send(Outbound, NS, P, Data), + receive + {udp,Outbound,NS,P,Reply} -> + {ok,Msg} = inet_dns:decode(Reply), + Hdr = inet_dns:msg(Msg, header), + Id = inet_dns:header(Hdr, id), + BadHdr = + inet_dns:make_header(Hdr, id, (Id+1) band 16#ffff), + BadMsg = inet_dns:make_msg(Msg, header, BadHdr), + BadReply = inet_dns:encode(BadMsg), + receive + {time,Tag} -> + proxy__last_ms_answer( + Inbound, SrcIP, SrcPort, BadReply, Reply, 30) + end + end + end. + +proxy__last_ms_answer(Socket, IP, Port, _, Reply, 0) -> + ok = gen_udp:send(Socket, IP, Port, Reply); +proxy__last_ms_answer(Socket, IP, Port, BadReply, Reply, N) -> + ok = gen_udp:send(Socket, IP, Port, BadReply), + receive after 1 -> ok end, + proxy__last_ms_answer(Socket, IP, Port, BadReply, Reply, N-1). + +proxy_wait({proxy,Pid,_,_}) -> + Mref = erlang:monitor(process, Pid), + receive {'DOWN',Mref,_,_,_} -> ok end. + +proxy_ns({proxy,_,_,ProxyNS}) -> ProxyNS. + %% %% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -204,7 +287,7 @@ basic(Config) when is_list(Config) -> {ok,Msg1} = inet_dns:decode(Bin1), %% %% resolve - {ok,Msg2} = inet_res:resolve(Name, in, a, [{nameservers,[NS]}]), + {ok,Msg2} = inet_res:resolve(Name, in, a, [{nameservers,[NS]},verbose]), io:format("~p~n", [Msg2]), [RR2] = inet_dns:msg(Msg2, anlist), IP = inet_dns:rr(RR2, data), @@ -474,6 +557,26 @@ do_files_monitor(Config) -> ok. %% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +last_ms_answer(doc) -> + ["Answer just when timeout is triggered (OTP-9221)"]; +last_ms_answer(Config) when is_list(Config) -> + NS = ns(Config), + Name = "ns.otptest", + %%IP = {127,0,0,254}, + Time = inet_db:res_option(timeout) div inet_db:res_option(retry), + PSpec = proxy_start(last_ms_answer, NS), + ProxyNS = proxy_ns(PSpec), + %% + %% resolve; whith short timeout to trigger Timeout =:= 0 in inet_res + {error,timeout} = + inet_res:resolve( + Name, in, a, [{nameservers,[ProxyNS]},verbose], Time + 10), + %% + proxy_wait(PSpec), + ok. + +%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% Compatibility tests. Call the inet_SUITE tests, but with %% lookup = [file,dns] instead of [native] diff --git a/lib/ssh/doc/src/notes.xml b/lib/ssh/doc/src/notes.xml index 224b9d4af7..4f546a37ed 100644 --- a/lib/ssh/doc/src/notes.xml +++ b/lib/ssh/doc/src/notes.xml @@ -29,6 +29,20 @@ <file>notes.xml</file> </header> +<section><title>Ssh 2.0.6</title> + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + A memory leak has been fixed. I.e. per terminated connection the size of + a pid and the length of a user name string was not cleared.</p> + <p> + Own Id: OTP-9232</p> + </item> + </list> + </section> +</section> + <section><title>Ssh 2.0.5</title> <section><title>Improvements and New Features</title> <list> diff --git a/lib/ssh/src/ssh.appup.src b/lib/ssh/src/ssh.appup.src index 9be8c3c7d5..37f24e2463 100644 --- a/lib/ssh/src/ssh.appup.src +++ b/lib/ssh/src/ssh.appup.src @@ -19,44 +19,12 @@ {"%VSN%", [ - {"2.0.4", [{load_module, ssh_bits, soft_purge, soft_purge, []}, - {load_module, ssh_connection_handler, soft_purge, soft_purge, []}]}, - {"2.0.3", [{load_module, ssh_bits, soft_purge, soft_purge, []}, - {load_module, ssh_connection_handler, soft_purge, soft_purge, []}, - {load_module, ssh_file, soft_purge, soft_purge, []}, - {load_module, ssh, soft_purge, soft_purge, []}, - {load_module, ssh_rsa, soft_purge, soft_purge, []}, - {load_module, ssh_acceptor, soft_purge, soft_purge, []}, - {load_module, ssh_transport, soft_purge, soft_purge, []}, - {load_module, ssh_connection_manager, soft_purge, soft_purge, []}]}, - {"2.0.2", [{load_module, ssh_bits, soft_purge, soft_purge, []}, - {load_module, ssh_connection_handler, soft_purge, soft_purge, []}, - {load_module, ssh_file, soft_purge, soft_purge, []}, - {load_module, ssh, soft_purge, soft_purge, []}, - {load_module, ssh_rsa, soft_purge, soft_purge, []}, - {load_module, ssh_acceptor, soft_purge, soft_purge, []}, - {load_module, ssh_transport, soft_purge, soft_purge, []}, - {load_module, ssh_connection_manager, soft_purge, soft_purge, []}]} + {"2.0.5", [{load_module, ssh_userreg, soft_purge, soft_purge, []}, + {load_module, ssh_connection_handler, soft_purge, soft_purge, [ssh_userreg]}]} ], [ - {"2.0.4", [{load_module, ssh_bits, soft_purge, soft_purge, []}, - {load_module, ssh_connection_handler, soft_purge, soft_purge, []}]}, - {"2.0.3", [{load_module, ssh_bits, soft_purge, soft_purge, []}, - {load_module, ssh_connection_handler, soft_purge, soft_purge, []}, - {load_module, ssh_file, soft_purge, soft_purge, []}, - {load_module, ssh, soft_purge, soft_purge, []}, - {load_module, ssh_rsa, soft_purge, soft_purge, []}, - {load_module, ssh_acceptor, soft_purge, soft_purge, []}, - {load_module, ssh_transport, soft_purge, soft_purge, []}, - {load_module, ssh_connection_manager, soft_purge, soft_purge, []}]}, - {"2.0.2", [{load_module, ssh_bits, soft_purge, soft_purge, []}, - {load_module, ssh_connection_handler, soft_purge, soft_purge, []}, - {load_module, ssh_file, soft_purge, soft_purge, []}, - {load_module, ssh, soft_purge, soft_purge, []}, - {load_module, ssh_rsa, soft_purge, soft_purge, []}, - {load_module, ssh_acceptor, soft_purge, soft_purge, []}, - {load_module, ssh_transport, soft_purge, soft_purge, []}, - {load_module, ssh_connection_manager, soft_purge, soft_purge, []}]} + {"2.0.5", [{load_module, ssh_userreg, soft_purge, soft_purge, []}, + {load_module, ssh_connection_handler, soft_purge, soft_purge, [ssh_userreg]}]} ] }. diff --git a/lib/ssh/src/ssh_cli.erl b/lib/ssh/src/ssh_cli.erl index cb78acb84c..781e01b9d1 100644 --- a/lib/ssh/src/ssh_cli.erl +++ b/lib/ssh/src/ssh_cli.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2005-2010. All Rights Reserved. +%% Copyright Ericsson AB 2005-2011. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in diff --git a/lib/ssh/src/ssh_connection_handler.erl b/lib/ssh/src/ssh_connection_handler.erl index 2d82e6d77d..3193be2510 100644 --- a/lib/ssh/src/ssh_connection_handler.erl +++ b/lib/ssh/src/ssh_connection_handler.erl @@ -578,7 +578,9 @@ handle_info({CloseTag, _Socket}, _StateName, %% Reason. The return value is ignored. %%-------------------------------------------------------------------- terminate(normal, _, #state{transport_cb = Transport, - socket = Socket}) -> + socket = Socket, + manager = Pid}) -> + (catch ssh_userreg:delete_user(Pid)), (catch Transport:close(Socket)), ok; @@ -810,7 +812,7 @@ handle_disconnect(#ssh_msg_disconnect{} = Msg, #state{ssh_params = Ssh0, manager = Pid} = State) -> {SshPacket, Ssh} = ssh_transport:ssh_packet(Msg, Ssh0), try - send_msg(SshPacket, State), + send_msg(SshPacket, State), ssh_connection_manager:event(Pid, Msg) catch exit:{noproc, _Reason} -> @@ -822,6 +824,7 @@ handle_disconnect(#ssh_msg_disconnect{} = Msg, [Msg, Exit]), error_logger:info_report(Report) end, + (catch ssh_userreg:delete_user(Pid)), {stop, normal, State#state{ssh_params = Ssh}}. counterpart_versions(NumVsn, StrVsn, #ssh{role = server} = Ssh) -> diff --git a/lib/ssh/src/ssh_userreg.erl b/lib/ssh/src/ssh_userreg.erl index 33c801f490..f901461aea 100644 --- a/lib/ssh/src/ssh_userreg.erl +++ b/lib/ssh/src/ssh_userreg.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2008-2010. All Rights Reserved. +%% Copyright Ericsson AB 2008-2011. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -25,11 +25,18 @@ -behaviour(gen_server). %% API --export([start_link/0, register_user/2, lookup_user/1]). +-export([start_link/0, + register_user/2, + lookup_user/1, + delete_user/1]). %% gen_server callbacks --export([init/1, handle_call/3, handle_cast/2, handle_info/2, - terminate/2, code_change/3]). +-export([init/1, + handle_call/3, + handle_cast/2, + handle_info/2, + terminate/2, + code_change/3]). -record(state, {user_db = []}). @@ -46,6 +53,9 @@ start_link() -> register_user(User, Cm) -> gen_server:cast(?MODULE, {register, {User, Cm}}). +delete_user(Cm) -> + gen_server:cast(?MODULE, {delete, Cm}). + lookup_user(Cm) -> gen_server:call(?MODULE, {get_user, Cm}, infinity). @@ -82,9 +92,10 @@ handle_call({get_user, Cm}, _From, #state{user_db = Db} = State) -> %% {stop, Reason, State} %% Description: Handling cast messages %%-------------------------------------------------------------------- -handle_cast({register, UserCm}, State0) -> - State = insert(UserCm, State0), - {noreply, State}. +handle_cast({register, UserCm}, State) -> + {noreply, insert(UserCm, State)}; +handle_cast({delete, UserCm}, State) -> + {noreply, delete(UserCm, State)}. %%-------------------------------------------------------------------- %% Function: handle_info(Info, State) -> {noreply, State} | @@ -118,6 +129,9 @@ code_change(_OldVsn, State, _Extra) -> insert({User, Cm}, #state{user_db = Db} = State) -> State#state{user_db = [{User, Cm} | Db]}. +delete(Cm, #state{user_db = Db} = State) -> + State#state{user_db = lists:keydelete(Cm, 2, Db)}. + lookup(_, []) -> undefined; lookup(Cm, [{User, Cm} | _Rest]) -> diff --git a/lib/ssh/vsn.mk b/lib/ssh/vsn.mk index 8c9f671fd5..d0861b3ddc 100644 --- a/lib/ssh/vsn.mk +++ b/lib/ssh/vsn.mk @@ -1,5 +1,5 @@ #-*-makefile-*- ; force emacs to enter makefile-mode -SSH_VSN = 2.0.5 +SSH_VSN = 2.0.6 APP_VSN = "ssh-$(SSH_VSN)" diff --git a/lib/ssl/doc/src/ssl.xml b/lib/ssl/doc/src/ssl.xml index 60ea4d547f..0da6bbee5b 100644 --- a/lib/ssl/doc/src/ssl.xml +++ b/lib/ssl/doc/src/ssl.xml @@ -53,13 +53,11 @@ <p>The following data types are used in the functions below: </p> - <p><c>boolean() = true | false</c></p> - - <p><c>property() = atom()</c></p> - + <p><c>boolean() = true | false</c></p> + <p><c>option() = socketoption() | ssloption() | transportoption()</c></p> - <p><c>socketoption() = [{property(), term()}] - defaults to + <p><c>socketoption() = proplists:property() - The default socket options are [{mode,list},{packet, 0},{header, 0},{active, true}]. </c></p> @@ -488,7 +486,7 @@ fun(OtpCert :: #'OTPCertificate'{}, Event :: {bad_cert, Reason :: atom()} | <fsummary>Get the value of the specified options.</fsummary> <type> <v>Socket = sslsocket()</v> - <v>OptionNames = [property()]</v> + <v>OptionNames = [atom()]</v> </type> <desc> <p>Get the value of the specified socket options, if no @@ -583,7 +581,7 @@ fun(OtpCert :: #'OTPCertificate'{}, Event :: {bad_cert, Reason :: atom()} | <fsummary>Write data to a socket.</fsummary> <type> <v>Socket = sslsocket()</v> - <v>Data = iolist() | binary()</v> + <v>Data = iodata()</v> </type> <desc> <p>Writes <c>Data</c> to <c>Socket</c>. </p> diff --git a/lib/ssl/src/ssl.erl b/lib/ssl/src/ssl.erl index 7b1fda4cf9..380c59b058 100644 --- a/lib/ssl/src/ssl.erl +++ b/lib/ssl/src/ssl.erl @@ -50,8 +50,7 @@ cb %% Callback info }). -type option() :: socketoption() | ssloption() | transportoption(). --type socketoption() :: [{property(), term()}]. %% See gen_tcp and inet --type property() :: atom(). +-type socketoption() :: term(). %% See gen_tcp and inet, import spec later when there is one to import -type ssloption() :: {verify, verify_type()} | {verify_fun, {fun(), InitialUserState::term()}} | {fail_if_no_peer_cert, boolean()} | {depth, integer()} | @@ -264,7 +263,7 @@ close(Socket = #sslsocket{}) -> ssl_broker:close(Socket). %%-------------------------------------------------------------------- --spec send(#sslsocket{}, iolist()) -> ok | {error, reason()}. +-spec send(#sslsocket{}, iodata()) -> ok | {error, reason()}. %% %% Description: Sends data over the ssl connection %%-------------------------------------------------------------------- @@ -403,9 +402,9 @@ cipher_suites(openssl) -> [ssl_cipher:openssl_suite_name(S) || S <- ssl_cipher:suites(Version)]. %%-------------------------------------------------------------------- --spec getopts(#sslsocket{}, [atom()]) -> {ok, [{atom(), term()}]}| {error, reason()}. +-spec getopts(#sslsocket{}, [atom()]) -> {ok, [{atom(), term()}]} | {error, reason()}. %% -%% Description: +%% Description: Gets options %%-------------------------------------------------------------------- getopts(#sslsocket{fd = new_ssl, pid = Pid}, OptTags) when is_pid(Pid) -> ssl_connection:get_opts(Pid, OptTags); @@ -416,9 +415,9 @@ getopts(#sslsocket{} = Socket, Options) -> ssl_broker:getopts(Socket, Options). %%-------------------------------------------------------------------- --spec setopts(#sslsocket{}, [{atom(), term()}]) -> ok | {error, reason()}. +-spec setopts(#sslsocket{}, [proplist:property()]) -> ok | {error, reason()}. %% -%% Description: +%% Description: Sets options %%-------------------------------------------------------------------- setopts(#sslsocket{fd = new_ssl, pid = Pid}, Opts0) when is_pid(Pid) -> Opts = proplists:expand([{binary, [{mode, binary}]}, diff --git a/lib/ssl/src/ssl_connection.erl b/lib/ssl/src/ssl_connection.erl index 574e1e9468..0a86e9bd29 100644 --- a/lib/ssl/src/ssl_connection.erl +++ b/lib/ssl/src/ssl_connection.erl @@ -107,12 +107,14 @@ %%==================================================================== %%-------------------------------------------------------------------- --spec send(pid(), iolist()) -> ok | {error, reason()}. +-spec send(pid(), iodata()) -> ok | {error, reason()}. %% %% Description: Sends data over the ssl connection %%-------------------------------------------------------------------- send(Pid, Data) -> sync_send_all_state_event(Pid, {application_data, + %% iolist_to_binary should really + %% be called iodata_to_binary() erlang:iolist_to_binary(Data)}, infinity). %%-------------------------------------------------------------------- diff --git a/lib/stdlib/doc/src/supervisor.xml b/lib/stdlib/doc/src/supervisor.xml index 45fa0847a8..d6203bdaa0 100644 --- a/lib/stdlib/doc/src/supervisor.xml +++ b/lib/stdlib/doc/src/supervisor.xml @@ -4,7 +4,7 @@ <erlref> <header> <copyright> - <year>1996</year><year>2010</year> + <year>1996</year><year>2011</year> <holder>Ericsson AB. All Rights Reserved.</holder> </copyright> <legalnotice> @@ -83,11 +83,17 @@ supervisor, where all child processes are dynamically added instances of the same process type, i.e. running the same code.</p> - <p>The functions <c>terminate_child/2</c>, <c>delete_child/2</c> + <p>The functions <c>delete_child/2</c> and <c>restart_child/2</c> are invalid for <c>simple_one_for_one</c> supervisors and will return <c>{error,simple_one_for_one}</c> if the specified supervisor uses this restart strategy.</p> + <p>The function <c>terminate_child/2</c> can be used for + children under <c>simple_one_for_one</c> supervisors by + giving the child's <c>pid()</c> as the second argument. If + instead the child specification identifier is used, + <c>terminate_child/2</c> will return + <c>{error,simple_one_for_one}</c>.</p> </item> </list> <p>To prevent a supervisor from getting into an infinite loop of @@ -311,24 +317,33 @@ child_spec() = {Id,StartFunc,Restart,Shutdown,Type,Modules} <type> <v>SupRef = Name | {Name,Node} | {global,Name} | pid()</v> <v> Name = Node = atom()</v> - <v>Id = term()</v> + <v>Id = pid() | term()</v> <v>Result = ok | {error,Error}</v> <v> Error = not_found | simple_one_for_one</v> </type> <desc> - <p>Tells the supervisor <c>SupRef</c> to terminate the child - process corresponding to the child specification identified - by <c>Id</c>. The process, if there is one, is terminated but - the child specification is kept by the supervisor. This means - that the child process may be later be restarted by - the supervisor. The child process can also be restarted - explicitly by calling <c>restart_child/2</c>. Use - <c>delete_child/2</c> to remove the child specification.</p> + <p>Tells the supervisor <c>SupRef</c> to terminate the given + child.</p> + <p>If the supervisor is not <c>simple_one_for_one</c>, + <c>Id</c> must be the child specification identifier. The + process, if there is one, is terminated but the child + specification is kept by the supervisor. The child process + may later be restarted by the supervisor. The child process + can also be restarted explicitly by calling + <c>restart_child/2</c>. Use <c>delete_child/2</c> to remove + the child specification.</p> + <p>If the supervisor is <c>simple_one_for_one</c>, <c>Id</c> + must be the child process' <c>pid()</c>. I the specified + process is alive, but is not a child of the given + supervisor, the function will return + <c>{error,not_found}</c>. If the child specification + identifier is given instead instead of a <c>pid()</c>, the + function will return <c>{error,simple_one_for_one}</c>.</p> + <p>If successful, the function returns <c>ok</c>. If there is + no child specification with the specified <c>Id</c>, the + function returns <c>{error,not_found}</c>.</p> <p>See <c>start_child/2</c> for a description of <c>SupRef</c>.</p> - <p>If successful, the function returns <c>ok</c>. If there is - no child specification with the specified <c>Id</c>, - the function returns <c>{error,not_found}</c>.</p> </desc> </func> <func> diff --git a/lib/stdlib/doc/src/unicode.xml b/lib/stdlib/doc/src/unicode.xml index e3a25a407b..cb1cfa8ed0 100644 --- a/lib/stdlib/doc/src/unicode.xml +++ b/lib/stdlib/doc/src/unicode.xml @@ -164,10 +164,16 @@ latin1_charlist() = [latin1_char() | latin1_binary() | latin1_charlist()] <item>Integers out of range - If <c>InEncoding</c> is <c>latin1</c>, an error occurs whenever an integer greater than 255 is found in the lists. If <c>InEncoding</c> is - of a Unicode type, error occurs whenever an integer greater than - <c>16#10FFFF</c> (the maximum unicode character) or in the - range <c>16#D800</c> to <c>16#DFFF</c> (invalid unicode - range) is found.</item> + of a Unicode type, an error occurs whenever an integer + <list type="bulleted"> + <item>greater than <c>16#10FFFF</c> + (the maximum unicode character),</item> + <item>in the range <c>16#D800</c> to <c>16#DFFF</c> + (invalid unicode range)</item> + <item>or equal to 16#FFFE or 16#FFFF (non characters)</item> + </list> + is found. + </item> <item>UTF encoding incorrect - If <c>InEncoding</c> is one of the UTF types, the bytes in any binaries have to be valid diff --git a/lib/stdlib/src/pool.erl b/lib/stdlib/src/pool.erl index 7f5f23e26d..a3c9927ee9 100644 --- a/lib/stdlib/src/pool.erl +++ b/lib/stdlib/src/pool.erl @@ -95,6 +95,9 @@ pspawn_link(M, F, A) -> start_nodes([], _, _) -> []; start_nodes([Host|Tail], Name, Args) -> case slave:start(Host, Name, Args) of + {error, {already_running, Node}} -> + io:format("Can't start node on host ~w due to ~w~n",[Host, {already_running, Node}]), + [Node | start_nodes(Tail, Name, Args)]; {error, R} -> io:format("Can't start node on host ~w due to ~w~n",[Host, R]), start_nodes(Tail, Name, Args); diff --git a/lib/stdlib/src/supervisor.erl b/lib/stdlib/src/supervisor.erl index 368dc2e3e5..4fd7f1d47c 100644 --- a/lib/stdlib/src/supervisor.erl +++ b/lib/stdlib/src/supervisor.erl @@ -138,7 +138,7 @@ delete_child(Supervisor, Name) -> %%----------------------------------------------------------------- -type term_err() :: 'not_found' | 'simple_one_for_one'. --spec terminate_child(sup_ref(), term()) -> 'ok' | {'error', term_err()}. +-spec terminate_child(sup_ref(), pid() | term()) -> 'ok' | {'error', term_err()}. terminate_child(Supervisor, Name) -> call(Supervisor, {terminate_child, Name}). @@ -297,8 +297,26 @@ handle_call({start_child, EArgs}, _From, State) when ?is_simple(State) -> {reply, What, State} end; -%%% The requests terminate_child, delete_child and restart_child are -%%% invalid for simple_one_for_one supervisors. +%% terminate_child for simple_one_for_one can only be done with pid +handle_call({terminate_child, Name}, _From, State) when not is_pid(Name), + ?is_simple(State) -> + {reply, {error, simple_one_for_one}, State}; + +handle_call({terminate_child, Name}, _From, State) -> + case get_child(Name, State, ?is_simple(State)) of + {value, Child} -> + case do_terminate(Child, State#state.name) of + #child{restart_type=RT} when RT=:=temporary; ?is_simple(State) -> + {reply, ok, state_del_child(Child, State)}; + NChild -> + {reply, ok, replace_child(NChild, State)} + end; + false -> + {reply, {error, not_found}, State} + end; + +%%% The requests delete_child and restart_child are invalid for +%%% simple_one_for_one supervisors. handle_call({_Req, _Data}, _From, State) when ?is_simple(State) -> {reply, {error, simple_one_for_one}, State}; @@ -341,19 +359,6 @@ handle_call({delete_child, Name}, _From, State) -> {reply, {error, not_found}, State} end; -handle_call({terminate_child, Name}, _From, State) -> - case get_child(Name, State) of - {value, Child} -> - case do_terminate(Child, State#state.name) of - #child{restart_type = temporary} = NChild -> - {reply, ok, state_del_child(NChild, State)}; - NChild -> - {reply, ok, replace_child(NChild, State)} - end; - _ -> - {reply, {error, not_found}, State} - end; - handle_call(which_children, _From, #state{children = [#child{restart_type = temporary, child_type = CT, modules = Mods}]} = @@ -849,7 +854,28 @@ split_child(_, [], After) -> {lists:reverse(After), []}. get_child(Name, State) -> + get_child(Name, State, false). +get_child(Pid, State, AllowPid) when AllowPid, is_pid(Pid) -> + get_dynamic_child(Pid, State); +get_child(Name, State, _) -> lists:keysearch(Name, #child.name, State#state.children). + +get_dynamic_child(Pid, #state{children=[Child], dynamics=Dynamics}) -> + case is_dynamic_pid(Pid, dynamics_db(Child#child.restart_type, Dynamics)) of + true -> + {value, Child#child{pid=Pid}}; + false -> + case erlang:is_process_alive(Pid) of + true -> false; + false -> {value, Child} + end + end. + +is_dynamic_pid(Pid, Dynamics) when is_list(Dynamics) -> + lists:member(Pid, Dynamics); +is_dynamic_pid(Pid, Dynamics) -> + dict:is_key(Pid, Dynamics). + replace_child(Child, State) -> Chs = do_replace_child(Child, State#state.children), State#state{children = Chs}. diff --git a/lib/stdlib/test/supervisor_SUITE.erl b/lib/stdlib/test/supervisor_SUITE.erl index f9ceed8f84..cc271bd047 100644 --- a/lib/stdlib/test/supervisor_SUITE.erl +++ b/lib/stdlib/test/supervisor_SUITE.erl @@ -20,7 +20,7 @@ -module(supervisor_SUITE). --include_lib("test_server/include/test_server.hrl"). +-include_lib("common_test/include/ct.hrl"). -define(TIMEOUT, 1000). %% Testserver specific export @@ -349,8 +349,7 @@ child_adm(Config) when is_list(Config) -> ok = supervisor:terminate_child(sup_test, child1), %% Start of already existing but not running process - {error,already_present} = - supervisor:start_child(sup_test, Child), + {error,already_present} = supervisor:start_child(sup_test, Child), %% Restart {ok, CPid2} = supervisor:restart_child(sup_test, child1), @@ -377,6 +376,11 @@ child_adm(Config) when is_list(Config) -> [{child1, CPid3, worker, []}] = supervisor:which_children(sup_test), [1,1,0,1] = get_child_counts(sup_test), + %% Terminate with Pid not allowed when not simple_one_for_one + {error,not_found} = supervisor:terminate_child(sup_test, CPid3), + [{child1, CPid3, worker, []}] = supervisor:which_children(sup_test), + [1,1,0,1] = get_child_counts(sup_test), + {'EXIT',{noproc,{gen_server,call,[foo,which_children,infinity]}}} = (catch supervisor:which_children(foo)), {'EXIT',{noproc,{gen_server,call,[foo,count_children,infinity]}}} @@ -412,16 +416,26 @@ child_adm_simple(Config) when is_list(Config) -> [1,2,0,2] = get_child_counts(sup_test), %% Termination - {error, simple_one_for_one} = - supervisor:terminate_child(sup_test, child1), + {error, simple_one_for_one} = supervisor:terminate_child(sup_test, child1), + [1,2,0,2] = get_child_counts(sup_test), + ok = supervisor:terminate_child(sup_test,CPid1), + [_] = supervisor:which_children(sup_test), + [1,1,0,1] = get_child_counts(sup_test), + false = erlang:is_process_alive(CPid1), + %% Terminate non-existing proccess is ok + ok = supervisor:terminate_child(sup_test,CPid1), + [_] = supervisor:which_children(sup_test), + [1,1,0,1] = get_child_counts(sup_test), + %% Terminate pid which is not a child of this supervisor is not ok + NoChildPid = spawn_link(fun() -> receive after infinity -> ok end end), + {error, not_found} = supervisor:terminate_child(sup_test, NoChildPid), + true = erlang:is_process_alive(NoChildPid), %% Restart - {error, simple_one_for_one} = - supervisor:restart_child(sup_test, child1), + {error, simple_one_for_one} = supervisor:restart_child(sup_test, child1), %% Deletion - {error, simple_one_for_one} = - supervisor:delete_child(sup_test, child1), + {error, simple_one_for_one} = supervisor:delete_child(sup_test, child1), ok. %%------------------------------------------------------------------------- diff --git a/lib/test_server/src/test_server.erl b/lib/test_server/src/test_server.erl index 8fe7d72270..591329b361 100644 --- a/lib/test_server/src/test_server.erl +++ b/lib/test_server/src/test_server.erl @@ -36,7 +36,7 @@ -export([capture_start/0,capture_stop/0,capture_get/0]). -export([messages_get/0]). -export([hours/1,minutes/1,seconds/1,sleep/1,adjusted_sleep/1,timecall/3]). --export([timetrap_scale_factor/0,timetrap/1,timetrap_cancel/1]). +-export([timetrap_scale_factor/0,timetrap/1,timetrap_cancel/1,timetrap_cancel/0]). -export([m_out_of_n/3,do_times/4,do_times/2]). -export([call_crash/3,call_crash/4,call_crash/5]). -export([temp_name/1]). @@ -1077,7 +1077,7 @@ run_test_case_eval(Mod, Func, Args0, Name, Ref, RunInit, {{Time,Value},Loc,Opts} = case test_server_sup:framework_call(init_tc,[?pl2a(Mod),Func,Args0], - {ok, Args0}) of + {ok,Args0}) of {ok,Args} -> run_test_case_eval1(Mod, Func, Args, Name, RunInit, TCCallback); Error = {error,_Reason} -> @@ -1085,18 +1085,17 @@ run_test_case_eval(Mod, Func, Args0, Name, Ref, RunInit, {skip,{failed,Error}}), {{0,NewResult},{Mod,Func},[]}; {fail,Reason} -> - [Conf] = Args0, - Conf1 = [{tc_status,{failed,Reason}} | Conf], + Conf = [{tc_status,{failed,Reason}} | hd(Args0)], fw_error_notify(Mod, Func, Conf, Reason), - NewResult = do_end_tc_call(Mod,Func, {{error,Reason},[Conf1]}, - {fail, Reason}), + NewResult = do_end_tc_call(Mod,Func, {{error,Reason},[Conf]}, + {fail,Reason}), {{0,NewResult},{Mod,Func},[]}; Skip = {skip,_Reason} -> NewResult = do_end_tc_call(Mod,Func,{Skip,Args0},Skip), {{0,NewResult},{Mod,Func},[]}; {auto_skip,Reason} -> NewResult = do_end_tc_call(Mod, Func, {{skip,Reason},Args0}, - {skip, {fw_auto_skip,Reason}}), + {skip,{fw_auto_skip,Reason}}), {{0,NewResult},{Mod,Func},[]} end, exit({Ref,Time,Value,Loc,Opts}). @@ -1116,9 +1115,15 @@ run_test_case_eval1(Mod, Func, Args, Name, RunInit, TCCallback) -> {skip_and_save,Reason,SaveCfg} -> Line = get_loc(), Conf = [{tc_status,{skipped,Reason}},{save_config,SaveCfg}], - NewRes = do_end_tc_call(Mod, Func, {{skip, Reason}, [Conf]}, + NewRes = do_end_tc_call(Mod, Func, {{skip,Reason},[Conf]}, {skip, Reason}), {{0,NewRes},Line,[]}; + FailTC = {fail,Reason} -> % user fails the testcase + EndConf = [{tc_status,{failed,Reason}} | hd(Args)], + fw_error_notify(Mod, Func, EndConf, Reason), + NewRes = do_end_tc_call(Mod, Func, {{error,Reason},[EndConf]}, + FailTC), + {{0,NewRes},{Mod,Func},[]}; {ok,NewConf} -> put(test_server_init_or_end_conf,undefined), %% call user callback function if defined @@ -1153,8 +1158,9 @@ run_test_case_eval1(Mod, Func, Args, Name, RunInit, TCCallback) -> {FWReturn1,TSReturn1,EndConf2} = case end_per_testcase(Mod, Func, EndConf1) of SaveCfg1={save_config,_} -> - {FWReturn,TSReturn,[SaveCfg1|lists:keydelete(save_config, 1, EndConf1)]}; - {fail,ReasonToFail} -> % user has failed the testcase + {FWReturn,TSReturn,[SaveCfg1|lists:keydelete(save_config,1, + EndConf1)]}; + {fail,ReasonToFail} -> % user has failed the testcase fw_error_notify(Mod, Func, EndConf1, ReasonToFail), {{error,ReasonToFail},{failed,ReasonToFail},EndConf1}; {failed,{_,end_per_testcase,_}} = Failure -> % unexpected termination @@ -1301,7 +1307,7 @@ init_per_testcase(Mod, Func, Args) -> false -> code:load_file(Mod); _ -> ok end, -%% init_per_testcase defined, returns new configuration + %% init_per_testcase defined, returns new configuration case erlang:function_exported(Mod,init_per_testcase,2) of true -> case catch my_apply(Mod, init_per_testcase, [Func|Args]) of @@ -1321,6 +1327,8 @@ init_per_testcase(Mod, Func, Args) -> "bad elements in Config: ~p\n",[Bad]}, {skip,{failed,{Mod,init_per_testcase,bad_return}}} end; + {'$test_server_ok',Res={fail,_Reason}} -> + Res; {'$test_server_ok',_Other} -> group_leader() ! {printout,12, "ERROR! init_per_testcase did not return " @@ -1695,7 +1703,7 @@ fail() -> break(Comment) -> case erase(test_server_timetraps) of undefined -> ok; - List -> lists:foreach(fun(Ref) -> timetrap_cancel(Ref) end,List) + List -> lists:foreach(fun({Ref,_}) -> timetrap_cancel(Ref) end, List) end, io:format(user, "\n\n\n--- SEMIAUTOMATIC TESTING ---" @@ -1776,14 +1784,16 @@ timetrap(Timeout0) -> {undefined,false} -> timetrap1(Timeout, false); {undefined,_} -> timetrap1(Timeout, true); {infinity,_} -> infinity; + {_Int,_Scale} when Timeout == infinity -> infinity; {Int,Scale} -> timetrap1(Timeout*Int, Scale) end. timetrap1(Timeout, Scale) -> - Ref = spawn_link(test_server_sup,timetrap,[Timeout,Scale,self()]), + TCPid = self(), + Ref = spawn_link(test_server_sup,timetrap,[Timeout,Scale,TCPid]), case get(test_server_timetraps) of - undefined -> put(test_server_timetraps,[Ref]); - List -> put(test_server_timetraps,[Ref|List]) + undefined -> put(test_server_timetraps,[{Ref,TCPid}]); + List -> put(test_server_timetraps,[{Ref,TCPid}|List]) end, Ref. @@ -1796,14 +1806,16 @@ ensure_timetrap(Config) -> undefined -> ok; Garbage -> erase(test_server_default_timetrap), - format("=== WARNING: garbage in test_server_default_timetrap: ~p~n", + format("=== WARNING: garbage in " + "test_server_default_timetrap: ~p~n", [Garbage]) end, DTmo = case lists:keysearch(default_timeout,1,Config) of {value,{default_timeout,Tmo}} -> Tmo; _ -> ?DEFAULT_TIMETRAP_SECS end, - format("=== test_server setting default timetrap of ~p seconds~n", + format("=== test_server setting default " + "timetrap of ~p seconds~n", [DTmo]), put(test_server_default_timetrap, timetrap(seconds(DTmo))) end. @@ -1815,11 +1827,13 @@ cancel_default_timetrap() -> TimeTrap when is_pid(TimeTrap) -> timetrap_cancel(TimeTrap), erase(test_server_default_timetrap), - format("=== test_server canceled default timetrap since another timetrap was set~n"), + format("=== test_server canceled default timetrap " + "since another timetrap was set~n"), ok; Garbage -> erase(test_server_default_timetrap), - format("=== WARNING: garbage in test_server_default_timetrap: ~p~n", + format("=== WARNING: garbage in " + "test_server_default_timetrap: ~p~n", [Garbage]), error end. @@ -1833,6 +1847,7 @@ time_ms({Other,_N}) -> "Should be seconds, minutes, or hours.~n", [Other]), exit({invalid_time_spec,Other}); time_ms(Ms) when is_integer(Ms) -> Ms; +time_ms(infinity) -> infinity; time_ms(Other) -> exit({invalid_time_spec,Other}). @@ -1846,11 +1861,29 @@ timetrap_cancel(infinity) -> timetrap_cancel(Handle) -> case get(test_server_timetraps) of undefined -> ok; - [Handle] -> erase(test_server_timetraps); - List -> put(test_server_timetraps,lists:delete(Handle,List)) + [{Handle,_}] -> erase(test_server_timetraps); + Timers -> put(test_server_timetraps, + lists:keydelete(Handle, 1, Timers)) end, test_server_sup:timetrap_cancel(Handle). +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% timetrap_cancel() -> ok +%% +%% Cancels timetrap for current test case. +timetrap_cancel() -> + case get(test_server_timetraps) of + undefined -> + ok; + Timers -> + case lists:keysearch(self(), 2, Timers) of + {value,{Handle,_}} -> + timetrap_cancel(Handle); + _ -> + ok + end + end. + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% hours(N) -> Milliseconds %% minutes(N) -> Milliseconds diff --git a/lib/test_server/src/test_server_ctrl.erl b/lib/test_server/src/test_server_ctrl.erl index 30d7314058..de9b962dfc 100644 --- a/lib/test_server/src/test_server_ctrl.erl +++ b/lib/test_server/src/test_server_ctrl.erl @@ -1812,6 +1812,9 @@ start_log_file() -> ok = file:make_dir(PrivDir), put(test_server_priv_dir,PrivDir++"/"), print_timestamp(13,"Suite started at "), + + LogInfo = [{topdir,Dir},{rundir,lists:flatten(TestDir)}], + test_server_sup:framework_call(report, [loginfo,LogInfo]), ok. make_html_link(LinkName, Target, Explanation) -> @@ -1925,7 +1928,6 @@ html_convert_modules(TestSpec, _Config) -> copy_html_files(get(test_server_dir), get(test_server_log_dir_base)). %% Retrieve a list of modules out of the test spec. - html_isolate_modules(List) -> html_isolate_modules(List, sets:new()). html_isolate_modules([], Set) -> sets:to_list(Set); @@ -1939,37 +1941,56 @@ html_isolate_modules([{Mod,_Case,_Args}|Cases], Set) -> html_isolate_modules(Cases, sets:add_element(Mod, Set)). %% Given a list of modules, convert each module's source code to HTML. - html_convert_modules([Mod|Mods]) -> case code:which(Mod) of Path when is_list(Path) -> SrcFile = filename:rootname(Path) ++ ".erl", - DestDir = get(test_server_dir), - Name = atom_to_list(Mod), - DestFile = filename:join(DestDir, downcase(Name) ++ ?src_listing_ext), - html_possibly_convert(SrcFile, DestFile), - html_convert_modules(Mods); - _Other -> ok + FoundSrcFile = + case file:read_file_info(SrcFile) of + {ok,SInfo} -> + {SrcFile,SInfo}; + {error,_} -> + ModInfo = Mod:module_info(compile), + case proplists:get_value(source, ModInfo) of + undefined -> + undefined; + OtherSrcFile -> + case file:read_file_info(OtherSrcFile) of + {ok,SInfo} -> + {OtherSrcFile,SInfo}; + {error,_} -> + undefined + end + end + end, + case FoundSrcFile of + undefined -> + html_convert_modules(Mods); + {SrcFile1,SrcFileInfo} -> + DestDir = get(test_server_dir), + Name = atom_to_list(Mod), + DestFile = filename:join(DestDir, + downcase(Name)++?src_listing_ext), + html_possibly_convert(SrcFile1, SrcFileInfo, DestFile), + html_convert_modules(Mods) + end; + _Other -> + html_convert_modules(Mods) end; html_convert_modules([]) -> ok. %% Convert source code to HTML if possible and needed. - -html_possibly_convert(Src, Dest) -> - case file:read_file_info(Src) of - {ok,SInfo} -> - case file:read_file_info(Dest) of - {error,_Reason} -> % no dest file - erl2html2:convert(Src, Dest); - {ok,DInfo} when DInfo#file_info.mtime < SInfo#file_info.mtime -> - erl2html2:convert(Src, Dest); - {ok,_DInfo} -> ok % dest file up to date - end; - {error,_Reason} -> ok % no source code found +html_possibly_convert(Src, SrcInfo, Dest) -> + case file:read_file_info(Dest) of + {error,_Reason} -> % no dest file + erl2html2:convert(Src, Dest); + {ok,DestInfo} when DestInfo#file_info.mtime < SrcInfo#file_info.mtime -> + erl2html2:convert(Src, Dest); + {ok,_DestInfo} -> + ok % dest file up to date end. %% Copy all HTML files in InDir to OutDir. - copy_html_files(InDir, OutDir) -> Files = filelib:wildcard(filename:join(InDir, "*" ++ ?src_listing_ext)), lists:foreach(fun (Src) -> copy_html_file(Src, OutDir) end, Files). diff --git a/lib/test_server/src/test_server_sup.erl b/lib/test_server/src/test_server_sup.erl index 1a614d74d5..53dfb45e3a 100644 --- a/lib/test_server/src/test_server_sup.erl +++ b/lib/test_server/src/test_server_sup.erl @@ -83,13 +83,13 @@ timetrap(Timeout0, Scale, Pid) -> %% Handle = term() %% %% Cancels a time trap. - timetrap_cancel(Handle) -> unlink(Handle), MonRef = erlang:monitor(process, Handle), exit(Handle, kill), receive {'DOWN',MonRef,_,_,_} -> ok after 2000 -> ok end. + capture_get(Msgs) -> receive {captured,Msg} -> diff --git a/lib/test_server/src/ts_install.erl b/lib/test_server/src/ts_install.erl index 2ddffccf5b..8332ccfb40 100644 --- a/lib/test_server/src/ts_install.erl +++ b/lib/test_server/src/ts_install.erl @@ -22,6 +22,7 @@ -export([install/2, platform_id/1]). -include("ts.hrl"). +-include_lib("kernel/include/file.hrl"). install(install_local, Options) -> install(os:type(), Options); @@ -150,11 +151,17 @@ add_vars(Vars0, Opts0) -> end, {PlatformId, PlatformLabel, PlatformFilename, Version} = platform([{longnames, LongNames}|Vars0]), + NetDir = lists:concat(["/net", hostname()]), + Mounted = case file:read_file_info(NetDir) of + {ok, #file_info{type = directory}} -> NetDir; + _ -> "" + end, {Opts, [{longnames, LongNames}, {platform_id, PlatformId}, {platform_filename, PlatformFilename}, {rsh_name, get_rsh_name()}, {platform_label, PlatformLabel}, + {ts_net_dir, Mounted}, {erl_flags, []}, {erl_release, Version}, {ts_testcase_callback, get_testcase_callback()} | Vars0]}. diff --git a/lib/test_server/src/ts_run.erl b/lib/test_server/src/ts_run.erl index 067961a216..d145290820 100644 --- a/lib/test_server/src/ts_run.erl +++ b/lib/test_server/src/ts_run.erl @@ -212,6 +212,12 @@ make_command(Vars, Spec, State) -> false -> ok end, + + %% If Common Test specific variables are needed, add them here + %% on form: "{key1,value1}" "{key2,value2}" ... + NetDir = ts_lib:var(ts_net_dir, Vars), + TestVars = [ "\"{net_dir,\\\"",NetDir,"\\\"}\"" ], + %% NOTE: Do not use ' in these commands as it wont work on windows Cmd = [Erl, Naming, "test_server" " -rsh ", ts_lib:var(rsh_name, Vars), @@ -224,6 +230,7 @@ make_command(Vars, Spec, State) -> %% " -test_server_format_exception false", " -boot start_sasl -sasl errlog_type error", " -pz ",Cwd, + " -ct_test_vars ",TestVars, " -eval \"file:set_cwd(\\\"",TestDir,"\\\")\" " " -eval \"ct:run_test(", backslashify(lists:flatten(State#state.test_server_args)),")\"" @@ -369,7 +376,6 @@ make_common_test_args(Args0, Options, _Vars) -> end, ConfigFiles = [{config,[filename:join(ConfigPath,File) || File <- get_config_files()]}], - io_lib:format("~100000p",[Args0++Trace++Cover++Logdir++ ConfigFiles++Options]). diff --git a/lib/tools/emacs/erlang.el b/lib/tools/emacs/erlang.el index e1c0d31371..6728bef2a4 100644 --- a/lib/tools/emacs/erlang.el +++ b/lib/tools/emacs/erlang.el @@ -386,7 +386,8 @@ then no prototype is inserted. The test is performed by the function `erlang-test-criteria-list'.") (defvar erlang-electric-arrow-criteria - '(erlang-next-lines-empty-p + '(erlang-stop-when-in-type-spec + erlang-next-lines-empty-p erlang-at-end-of-function-p) "*List of functions controlling the arrow aspect of `erlang-electric-gt'. The functions in this list are called, in order, whenever a `>' @@ -4045,6 +4046,16 @@ This function is designed to be a member of a criteria list." nil))) +(defun erlang-stop-when-in-type-spec () + "Return `stop' when in a type spec line. + +This function is designed to be a member of a criteria list." + (save-excursion + (beginning-of-line) + (when (save-match-data (looking-at "-\\(spec\\|type\\)")) + 'stop))) + + (defun erlang-next-lines-empty-p () "Return non-nil if next lines are empty. diff --git a/lib/xmerl/src/xmerl_scan.erl b/lib/xmerl/src/xmerl_scan.erl index e07d495fc7..3a5164d154 100644 --- a/lib/xmerl/src/xmerl_scan.erl +++ b/lib/xmerl/src/xmerl_scan.erl @@ -102,6 +102,7 @@ %% declaration. </dd> %% </dl> + -module(xmerl_scan). -vsn('0.20'). -date('03-09-16'). @@ -2283,8 +2284,16 @@ scan_att_chars("&" ++ T, S0, Delim, Acc, TmpAcc,AT,IsNorm) -> % Reference true -> scan_att_chars(T1,S1,Delim,[ExpRef|Acc],[ExpRef|TmpAcc],AT,IsNorm); _ -> - scan_att_chars(string_to_char_set(S#xmerl_scanner.encoding,ExpRef) - ++ T1, S1, Delim, Acc,TmpAcc, AT,IsNorm) + Ch = string_to_char_set(S#xmerl_scanner.encoding, ExpRef), + case T of + "#" ++ _ -> + %% normalization rules (sec 3.3.3) require that for + %% character references, the referenced character be + %% added directly to the normalized value + scan_att_chars(T1, S1, Delim, Ch ++ Acc,TmpAcc, AT,IsNorm); + _ -> + scan_att_chars(Ch ++ T1, S1, Delim, Acc,TmpAcc, AT,IsNorm) + end end; scan_att_chars("<" ++ _T, S0, _Delim, _Acc,_, _,_) -> % Tags not allowed here ?fatal(unexpected_char, S0); diff --git a/system/doc/design_principles/sup_princ.xml b/system/doc/design_principles/sup_princ.xml index 067fd31961..2748f21bbe 100644 --- a/system/doc/design_principles/sup_princ.xml +++ b/system/doc/design_principles/sup_princ.xml @@ -4,7 +4,7 @@ <chapter> <header> <copyright> - <year>1997</year><year>2009</year> + <year>1997</year><year>2011</year> <holder>Ericsson AB. All Rights Reserved.</holder> </copyright> <legalnotice> @@ -335,6 +335,12 @@ supervisor:start_child(Pid, [id1])</code> <c>apply(call, start_link, []++[id1])</c>, or actually:</p> <code type="none"> call:start_link(id1)</code> + <p>A child under a <c>simple_one_for_one</c> supervisor can be terminated + with</p> + <code type="none"> +supervisor:terminate_child(Sup, Pid)</code> + <p>where <c>Sup</c> is the pid, or name, of the supervisor and + <c>Pid</c> is the pid of the child.</p> </section> <section> |