diff options
30 files changed, 1851 insertions, 330 deletions
diff --git a/bootstrap/lib/stdlib/ebin/erl_lint.beam b/bootstrap/lib/stdlib/ebin/erl_lint.beam Binary files differindex ac593149b8..ee07e7636c 100644 --- a/bootstrap/lib/stdlib/ebin/erl_lint.beam +++ b/bootstrap/lib/stdlib/ebin/erl_lint.beam diff --git a/erts/configure.in b/erts/configure.in index c47c211c4e..b056ba44e2 100644 --- a/erts/configure.in +++ b/erts/configure.in @@ -429,7 +429,7 @@ case $host_os in      win32)  	# The ethread library requires _WIN32_WINNT of at least 0x0403.  	# -D_WIN32_WINNT=* from CPPFLAGS is saved in ETHR_DEFS. -	CPPFLAGS="$CPPFLAGS -D_WIN32_WINNT=0x0500 -DWINVER=0x0500" +	CPPFLAGS="$CPPFLAGS -D_WIN32_WINNT=0x0501 -DWINVER=0x0501"  	;;      darwin*)          CPPFLAGS="$CPPFLAGS -D_XOPEN_SOURCE" diff --git a/erts/doc/src/erlang.xml b/erts/doc/src/erlang.xml index 7dc59ea954..767edc1cc0 100644 --- a/erts/doc/src/erlang.xml +++ b/erts/doc/src/erlang.xml @@ -6058,6 +6058,49 @@ ok  	      notice.  	      </p>            </item> +          <tag><c>{long_schedule, Time}</c></tag> +          <item> +              <p>If a process or port in the system runs uninterrupted +              for at least <c>Time</c> wall clock milliseconds, a +              message <c>{monitor, PidOrPort, long_schedule, Info}</c> +              is sent to <c>MonitorPid</c>. <c>PidOrPort</c> is the +              process or port that was running and <c>Info</c> is a +              list of two-element tuples describing the event. In case +              of a <c>pid()</c>, the tuples <c>{timeout, Millis}</c>, +              <c>{in, Location}</c> and <c>{out, Location}</c> will be +              present, where <c>Location</c> is either an MFA +              (<c>{Module, Function, Arity}</c>) describing the +              function where the process was scheduled in/out, or the +              atom <c>undefined</c>. In case of a <c>port()</c>, the +              tuples <c>{timeout, Millis}</c> and <c>{port_op,Op}</c> +              will be present. <c>Op</c> will be one of <c>proc_sig</c>, +              <c>timeout</c>, <c>input</c>, <c>output</c>, +              <c>event</c> or <c>dist_cmd</c>, depending on which +              driver callback was executing. <c>proc_sig</c> is an +              internal operation and should never appear, while the +              others represent the corresponding driver callbacks +              <c>timeout</c>, <c>ready_input</c>, <c>ready_output</c>, +              <c>event</c> and finally <c>outputv</c> (when the port +              is used by distribution).  The <c>Millis</c> value in +              the <c>timeout</c> tuple will tell you the actual +              uninterrupted execution time of the process or port, +              which will always be <c>>=</c> the <c>Time</c> value +              supplied when starting the trace. New tuples may be +              added to the <c>Info</c> list in the future, and the +              order of the tuples in the list may be changed at any +              time without prior notice. +	      </p> +	      <p>This can be used to detect problems with NIF's or +	      drivers that take too long to execute. Generally, 1 ms +	      is considered a good maximum time for a driver callback +	      or a NIF. However, a time sharing system should usually +	      consider everything below 100 ms as "possible" and +	      fairly "normal". Schedule times above that might however +	      indicate swapping or a NIF/driver that is +	      misbehaving. Misbehaving NIF's and drivers could cause +	      bad resource utilization and bad overall performance of +	      the system.</p> +          </item>            <tag><c>{large_heap, Size}</c></tag>            <item>              <p>If a garbage collection in the system results in diff --git a/erts/emulator/beam/atom.names b/erts/emulator/beam/atom.names index 7d86e486f1..3ee9eb0f88 100644 --- a/erts/emulator/beam/atom.names +++ b/erts/emulator/beam/atom.names @@ -178,6 +178,7 @@ atom disable_trace  atom disabled  atom display_items  atom dist +atom dist_cmd  atom Div='/'  atom div  atom dlink @@ -313,6 +314,7 @@ atom load_cancelled  atom load_failure  atom local  atom long_gc +atom long_schedule  atom low  atom Lt='<'  atom machine @@ -432,6 +434,7 @@ atom port  atom ports  atom port_count  atom port_limit +atom port_op  atom print  atom priority  atom private @@ -443,6 +446,7 @@ atom process_display  atom process_limit  atom process_dump  atom procs +atom proc_sig  atom profile  atom protected  atom protection diff --git a/erts/emulator/beam/beam_emu.c b/erts/emulator/beam/beam_emu.c index 944ed6da81..5781009f58 100644 --- a/erts/emulator/beam/beam_emu.c +++ b/erts/emulator/beam/beam_emu.c @@ -924,6 +924,7 @@ extern int count_instructions;  #  define NOINLINE  #endif +  /*   * The following functions are called directly by process_main().   * Don't inline them. @@ -1153,6 +1154,9 @@ void process_main(void)      Eterm pt_arity;		/* Used by do_put_tuple */ +    Uint64 start_time = 0;          /* Monitor long schedule */ +    BeamInstr* start_time_i = NULL; +      ERL_BITS_DECLARE_STATEP; /* Has to be last declaration */ @@ -1175,6 +1179,16 @@ void process_main(void)   do_schedule:      reds_used = REDS_IN(c_p) - FCALLS;   do_schedule1: + +    if (start_time != 0) { +        Sint64 diff = erts_timestamp_millis() - start_time; +	if (diff > 0 && (Uint) diff >  erts_system_monitor_long_schedule) { +	    BeamInstr *inptr = find_function_from_pc(start_time_i); +	    BeamInstr *outptr = find_function_from_pc(c_p->i); +	    monitor_long_schedule_proc(c_p,inptr,outptr,(Uint) diff); +	} +    } +      PROCESS_MAIN_CHK_LOCKS(c_p);      ERTS_SMP_UNREQ_PROC_MAIN_LOCK(c_p);  #if HALFWORD_HEAP @@ -1183,11 +1197,18 @@ void process_main(void)      ERTS_VERIFY_UNUSED_TEMP_ALLOC(c_p);      c_p = schedule(c_p, reds_used);      ERTS_VERIFY_UNUSED_TEMP_ALLOC(c_p); +    start_time = 0;  #ifdef DEBUG      pid = c_p->common.id; /* Save for debugging purpouses */  #endif      ERTS_SMP_REQ_PROC_MAIN_LOCK(c_p);      PROCESS_MAIN_CHK_LOCKS(c_p); + +    if (erts_system_monitor_long_schedule != 0) { +	start_time = erts_timestamp_millis(); +	start_time_i = c_p->i; +    } +      reg = ERTS_PROC_GET_SCHDATA(c_p)->x_reg_array;      freg = ERTS_PROC_GET_SCHDATA(c_p)->f_reg_array;  #if !HEAP_ON_C_STACK @@ -6151,6 +6172,7 @@ apply_fun(Process* p, Eterm fun, Eterm args, Eterm* reg)  } +  static Eterm  new_fun(Process* p, Eterm* reg, ErlFunEntry* fe, int num_free)  { diff --git a/erts/emulator/beam/erl_bif_trace.c b/erts/emulator/beam/erl_bif_trace.c index 559cb3efa1..06fbbea123 100644 --- a/erts/emulator/beam/erl_bif_trace.c +++ b/erts/emulator/beam/erl_bif_trace.c @@ -2012,6 +2012,7 @@ void erts_system_monitor_clear(Process *c_p) {  #endif      erts_set_system_monitor(NIL);      erts_system_monitor_long_gc = 0; +    erts_system_monitor_long_schedule = 0;      erts_system_monitor_large_heap = 0;      erts_system_monitor_flags.busy_port = 0;      erts_system_monitor_flags.busy_dist_port = 0; @@ -2036,12 +2037,17 @@ static Eterm system_monitor_get(Process *p)  	Uint hsz = 3 + (erts_system_monitor_flags.busy_dist_port ? 2 : 0) +  	    (erts_system_monitor_flags.busy_port ? 2 : 0);   	Eterm long_gc = NIL; +	Eterm long_schedule = NIL;  	Eterm large_heap = NIL;  	if (erts_system_monitor_long_gc != 0) {  	    hsz += 2+3;  	    (void) erts_bld_uint(NULL, &hsz, erts_system_monitor_long_gc);  	} +	if (erts_system_monitor_long_schedule != 0) { +	    hsz += 2+3; +	    (void) erts_bld_uint(NULL, &hsz, erts_system_monitor_long_schedule); +	}  	if (erts_system_monitor_large_heap != 0) {  	    hsz += 2+3;  	    (void) erts_bld_uint(NULL, &hsz, erts_system_monitor_large_heap); @@ -2051,6 +2057,10 @@ static Eterm system_monitor_get(Process *p)  	if (erts_system_monitor_long_gc != 0) {  	    long_gc = erts_bld_uint(&hp, NULL, erts_system_monitor_long_gc);  	} +	if (erts_system_monitor_long_schedule != 0) { +	    long_schedule = erts_bld_uint(&hp, NULL,  +					  erts_system_monitor_long_schedule); +	}  	if (erts_system_monitor_large_heap != 0) {  	    large_heap = erts_bld_uint(&hp, NULL, erts_system_monitor_large_heap);  	} @@ -2059,6 +2069,10 @@ static Eterm system_monitor_get(Process *p)  	    Eterm t = TUPLE2(hp, am_long_gc, long_gc); hp += 3;  	    res = CONS(hp, t, res); hp += 2;  	} +	if (long_schedule != NIL) { +	    Eterm t = TUPLE2(hp, am_long_schedule, long_schedule); hp += 3; +	    res = CONS(hp, t, res); hp += 2; +	}  	if (large_heap != NIL) {  	    Eterm t = TUPLE2(hp, am_large_heap, large_heap); hp += 3;  	    res = CONS(hp, t, res); hp += 2; @@ -2113,7 +2127,7 @@ system_monitor(Process *p, Eterm monitor_pid, Eterm list)      }      if (is_not_list(list)) goto error;      else { -	Uint long_gc, large_heap; +	Uint long_gc, long_schedule, large_heap;  	int busy_port, busy_dist_port;  	system_blocked = 1; @@ -2123,7 +2137,8 @@ system_monitor(Process *p, Eterm monitor_pid, Eterm list)  	if (!erts_pid2proc(p, ERTS_PROC_LOCK_MAIN, monitor_pid, 0))  	    goto error; -	for (long_gc = 0, large_heap = 0, busy_port = 0, busy_dist_port = 0; +	for (long_gc = 0, long_schedule = 0, large_heap = 0,  +		 busy_port = 0, busy_dist_port = 0;  	     is_list(list);  	     list = CDR(list_val(list))) {  	    Eterm t = CAR(list_val(list)); @@ -2133,6 +2148,9 @@ system_monitor(Process *p, Eterm monitor_pid, Eterm list)  		if (tp[1] == am_long_gc) {  		    if (! term_to_Uint(tp[2], &long_gc)) goto error;  		    if (long_gc < 1) long_gc = 1; +		} else if (tp[1] == am_long_schedule) { +		    if (! term_to_Uint(tp[2], &long_schedule)) goto error; +		    if (long_schedule < 1) long_schedule = 1;  		} else if (tp[1] == am_large_heap) {  		    if (! term_to_Uint(tp[2], &large_heap)) goto error;  		    if (large_heap < 16384) large_heap = 16384; @@ -2148,6 +2166,7 @@ system_monitor(Process *p, Eterm monitor_pid, Eterm list)  	prev = system_monitor_get(p);  	erts_set_system_monitor(monitor_pid);  	erts_system_monitor_long_gc = long_gc; +	erts_system_monitor_long_schedule = long_schedule;  	erts_system_monitor_large_heap = large_heap;  	erts_system_monitor_flags.busy_port = !!busy_port;  	erts_system_monitor_flags.busy_dist_port = !!busy_dist_port; diff --git a/erts/emulator/beam/erl_lock_check.c b/erts/emulator/beam/erl_lock_check.c index bc59fe55d4..2ac5e24d3a 100644 --- a/erts/emulator/beam/erl_lock_check.c +++ b/erts/emulator/beam/erl_lock_check.c @@ -180,6 +180,11 @@ static erts_lc_lock_order_t erts_lock_order[] = {      {   "efile_drv dtrace mutex",               NULL                    },  #endif      {	"mtrace_buf",				NULL			}, +#ifdef __WIN32__ +#ifdef ERTS_SMP +    {   "sys_gethrtime",                        NULL                    }, +#endif +#endif      {	"erts_alloc_hard_debug",		NULL			}  }; diff --git a/erts/emulator/beam/erl_port_task.c b/erts/emulator/beam/erl_port_task.c index f753de8f52..7d53ce7152 100644 --- a/erts/emulator/beam/erl_port_task.c +++ b/erts/emulator/beam/erl_port_task.c @@ -1594,6 +1594,7 @@ erts_port_task_execute(ErtsRunQueue *runq, Port **curr_port_pp)      int fpe_was_unmasked;      erts_aint32_t state;      int active; +    Uint64 start_time = 0;      ERTS_SMP_LC_ASSERT(erts_smp_lc_runq_is_locked(runq)); @@ -1655,6 +1656,10 @@ erts_port_task_execute(ErtsRunQueue *runq, Port **curr_port_pp)  	reset_handle(ptp); +	if (erts_system_monitor_long_schedule != 0) { +	    start_time = erts_timestamp_millis(); +	} +  	ERTS_SMP_LC_ASSERT(erts_lc_is_port_locked(pp));  	ERTS_SMP_CHK_NO_PROC_LOCKS;  	ASSERT(pp->drv_ptr); @@ -1723,6 +1728,14 @@ erts_port_task_execute(ErtsRunQueue *runq, Port **curr_port_pp)  	reds += erts_port_driver_callback_epilogue(pp, &state); +	if (start_time != 0) { +	    Sint64 diff = erts_timestamp_millis() - start_time; +	    if (diff > 0 && (Uint) diff >  erts_system_monitor_long_schedule) { +		monitor_long_schedule_port(pp,ptp->type,(Uint) diff); +	    } +	} +	start_time = 0; +      aborted_port_task:  	schedule_port_task_free(ptp); diff --git a/erts/emulator/beam/erl_process.c b/erts/emulator/beam/erl_process.c index 88eb224f84..3d161f2aa0 100644 --- a/erts/emulator/beam/erl_process.c +++ b/erts/emulator/beam/erl_process.c @@ -267,6 +267,7 @@ static Uint last_exact_reductions;  Uint erts_default_process_flags;  Eterm erts_system_monitor;  Eterm erts_system_monitor_long_gc; +Uint erts_system_monitor_long_schedule;  Eterm erts_system_monitor_large_heap;  struct erts_system_monitor_flags_t erts_system_monitor_flags; diff --git a/erts/emulator/beam/erl_process.h b/erts/emulator/beam/erl_process.h index 865ac6c43f..3c1edfad7a 100644 --- a/erts/emulator/beam/erl_process.h +++ b/erts/emulator/beam/erl_process.h @@ -1009,6 +1009,7 @@ extern erts_smp_rwmtx_t erts_cpu_bind_rwmtx;  */  extern Eterm erts_system_monitor;  extern Uint erts_system_monitor_long_gc; +extern Uint erts_system_monitor_long_schedule;  extern Uint erts_system_monitor_large_heap;  struct erts_system_monitor_flags_t {  	 unsigned int busy_port : 1; diff --git a/erts/emulator/beam/erl_trace.c b/erts/emulator/beam/erl_trace.c index 848877d43e..bb6ed44523 100644 --- a/erts/emulator/beam/erl_trace.c +++ b/erts/emulator/beam/erl_trace.c @@ -2268,7 +2268,134 @@ trace_gc(Process *p, Eterm what)  #undef LOCAL_HEAP_SIZE  } +void  +monitor_long_schedule_proc(Process *p, BeamInstr *in_fp, BeamInstr *out_fp, Uint time) +{ +    ErlHeapFragment *bp; +    ErlOffHeap *off_heap; +#ifndef ERTS_SMP +    Process *monitor_p; +#endif +    Uint hsz; +    Eterm *hp, list, in_mfa = am_undefined, out_mfa = am_undefined; +    Eterm in_tpl, out_tpl, tmo_tpl, tmo, msg; +  + +#ifndef ERTS_SMP +    ASSERT(is_internal_pid(system_monitor)); +    monitor_p = erts_proc_lookup(system_monitor); +    if (!monitor_p || p == monitor_p) { +	return; +    } +#endif +    /*  +     * Size: {monitor, pid, long_schedule, [{timeout, T}, {in, {M,F,A}},{out,{M,F,A}}]} -> +     * 5 (top tuple of 4), (3 (elements) * 2 (cons)) + 3 (timeout tuple of 2) + size of Timeout + +     * (2 * 3 (in/out tuple of 2)) +  +     * 0 (unknown) or 4 (MFA tuple of 3) + 0 (unknown) or 4 (MFA tuple of 3) +     * = 20 + (in_fp != NULL) ? 4 : 0 + (out_fp != NULL) ? 4 : 0 + size of Timeout +     */ +    hsz = 20 + ((in_fp != NULL) ? 4 : 0) + ((out_fp != NULL) ? 4 : 0); +    (void) erts_bld_uint(NULL, &hsz, time); +    hp = ERTS_ALLOC_SYSMSG_HEAP(hsz, &bp, &off_heap, monitor_p); +    tmo = erts_bld_uint(&hp, NULL, time); +    if (in_fp != NULL) { +	in_mfa = TUPLE3(hp,(Eterm) in_fp[0], (Eterm) in_fp[1], make_small(in_fp[2])); +	hp +=4; +    }  +    if (out_fp != NULL) { +	out_mfa = TUPLE3(hp,(Eterm) out_fp[0], (Eterm) out_fp[1], make_small(out_fp[2])); +	hp +=4; +    }  +    tmo_tpl = TUPLE2(hp,am_timeout, tmo); +    hp += 3; +    in_tpl = TUPLE2(hp,am_in,in_mfa); +    hp += 3; +    out_tpl = TUPLE2(hp,am_out,out_mfa); +    hp += 3; +    list = CONS(hp,out_tpl,NIL);  +    hp += 2; +    list = CONS(hp,in_tpl,list); +    hp += 2; +    list = CONS(hp,tmo_tpl,list); +    hp += 2; +    msg = TUPLE4(hp, am_monitor, p->common.id, am_long_schedule, list); +    hp += 5; +#ifdef ERTS_SMP +    enqueue_sys_msg(SYS_MSG_TYPE_SYSMON, p->common.id, NIL, msg, bp); +#else +    erts_queue_message(monitor_p, NULL, bp, msg, NIL +#ifdef USE_VM_PROBES +			   , NIL +#endif +		       ); +#endif +} +void  +monitor_long_schedule_port(Port *pp, ErtsPortTaskType type, Uint time) +{ +    ErlHeapFragment *bp; +    ErlOffHeap *off_heap; +#ifndef ERTS_SMP +    Process *monitor_p; +#endif +    Uint hsz; +    Eterm *hp, list, op; +    Eterm op_tpl, tmo_tpl, tmo, msg; +  + +#ifndef ERTS_SMP +    ASSERT(is_internal_pid(system_monitor)); +    monitor_p = erts_proc_lookup(system_monitor); +    if (!monitor_p) { +	return; +    } +#endif +    /*  +     * Size: {monitor, port, long_schedule, [{timeout, T}, {op, Operation}]} -> +     * 5 (top tuple of 4), (2 (elements) * 2 (cons)) + 3 (timeout tuple of 2)  +     * + size of Timeout + 3 (op tuple of 2 atoms) +     * = 15 + size of Timeout +     */ +    hsz = 15; +    (void) erts_bld_uint(NULL, &hsz, time); + +    hp = ERTS_ALLOC_SYSMSG_HEAP(hsz, &bp, &off_heap, monitor_p); +    switch (type) { +    case ERTS_PORT_TASK_PROC_SIG: op = am_proc_sig; break; +    case ERTS_PORT_TASK_TIMEOUT: op = am_timeout; break; +    case ERTS_PORT_TASK_INPUT: op = am_input; break; +    case ERTS_PORT_TASK_OUTPUT: op = am_output; break; +    case ERTS_PORT_TASK_EVENT: op = am_event; break; +    case ERTS_PORT_TASK_DIST_CMD: op = am_dist_cmd; break; +    default: op = am_undefined; break; +    } + +    tmo = erts_bld_uint(&hp, NULL, time); + +    op_tpl = TUPLE2(hp,am_port_op,op);  +    hp += 3; + +    tmo_tpl = TUPLE2(hp,am_timeout, tmo); +    hp += 3; + +    list = CONS(hp,op_tpl,NIL); +    hp += 2; +    list = CONS(hp,tmo_tpl,list); +    hp += 2; +    msg = TUPLE4(hp, am_monitor, pp->common.id, am_long_schedule, list); +    hp += 5; +#ifdef ERTS_SMP +    enqueue_sys_msg(SYS_MSG_TYPE_SYSMON, pp->common.id, NIL, msg, bp); +#else +    erts_queue_message(monitor_p, NULL, bp, msg, NIL +#ifdef USE_VM_PROBES +			   , NIL +#endif +		       ); +#endif +}  void  monitor_long_gc(Process *p, Uint time) { @@ -3011,6 +3138,7 @@ sys_msg_disp_failure(ErtsSysMsgQ *smqp, Eterm receiver)      case SYS_MSG_TYPE_SYSMON:  	if (receiver == NIL  	    && !erts_system_monitor_long_gc +	    && !erts_system_monitor_long_schedule  	    && !erts_system_monitor_large_heap  	    && !erts_system_monitor_flags.busy_port  	    && !erts_system_monitor_flags.busy_dist_port) diff --git a/erts/emulator/beam/erl_trace.h b/erts/emulator/beam/erl_trace.h index 50fb27aab0..54d3aafdda 100644 --- a/erts/emulator/beam/erl_trace.h +++ b/erts/emulator/beam/erl_trace.h @@ -83,6 +83,8 @@ void erts_system_profile_setup_active_schedulers(void);  /* system_monitor */  void monitor_long_gc(Process *p, Uint time); +void monitor_long_schedule_proc(Process *p, BeamInstr *in_i, BeamInstr *out_i, Uint time); +void monitor_long_schedule_port(Port *pp, ErtsPortTaskType type, Uint time);  void monitor_large_heap(Process *p);  void monitor_generic(Process *p, Eterm type, Eterm spec);  Uint erts_trace_flag2bit(Eterm flag); diff --git a/erts/emulator/beam/erl_unicode.c b/erts/emulator/beam/erl_unicode.c index ad6f8b993a..e00440b905 100644 --- a/erts/emulator/beam/erl_unicode.c +++ b/erts/emulator/beam/erl_unicode.c @@ -1723,14 +1723,14 @@ static BIF_RETTYPE do_bif_utf8_to_list(Process *p,      if (b_sz) {  	ErlSubBin *sb;  	Eterm orig; -	ERTS_DECLARE_DUMMY(Uint offset); +	Uint offset;  	ASSERT(state != ERTS_UTF8_OK);  	hp = HAlloc(p, ERL_SUB_BIN_SIZE);  	sb = (ErlSubBin *) hp;  	ERTS_GET_REAL_BIN(orig_bin, orig, offset, bitoffs, bitsize);  	sb->thing_word = HEADER_SUB_BIN;  	sb->size = b_sz; -	sb->offs = num_bytes_to_process + num_processed_bytes; +	sb->offs = offset + num_bytes_to_process + num_processed_bytes;  	sb->orig = orig;  	sb->bitoffs = bitoffs;  	sb->bitsize = bitsize; diff --git a/erts/emulator/beam/global.h b/erts/emulator/beam/global.h index d5e727bcba..12eb3bfb7c 100755 --- a/erts/emulator/beam/global.h +++ b/erts/emulator/beam/global.h @@ -740,6 +740,8 @@ void erl_drv_thr_init(void);  /* utils.c */  void erts_cleanup_offheap(ErlOffHeap *offheap); +Uint64 erts_timestamp_millis(void); +  Export* erts_find_function(Eterm, Eterm, unsigned int, ErtsCodeIndex);  Eterm store_external_or_ref_in_proc_(Process *, Eterm); diff --git a/erts/emulator/beam/utils.c b/erts/emulator/beam/utils.c index d5d97d748a..0a833f7e66 100644 --- a/erts/emulator/beam/utils.c +++ b/erts/emulator/beam/utils.c @@ -3722,6 +3722,24 @@ erts_smp_ensure_later_interval_acqb(erts_interval_t *icp, Uint64 ic)  } +/* + * A millisecond timestamp without time correction where there's no hrtime + * - for tracing on "long" things... + */ +Uint64 erts_timestamp_millis(void) +{ +#ifdef HAVE_GETHRTIME +    return (Uint64) (sys_gethrtime() / 1000000); +#else +    Uint64 res; +    SysTimeval tv; +    sys_gettimeofday(&tv); +    res = (Uint64) tv.tv_sec*1000000; +    res += (Uint64) tv.tv_usec; +    return (res / 1000); +#endif +} +  #ifdef DEBUG  /*   * Handy functions when using a debugger - don't use in the code! diff --git a/erts/emulator/sys/win32/sys_time.c b/erts/emulator/sys/win32/sys_time.c index 2f2dfc8197..f7f0161b58 100644 --- a/erts/emulator/sys/win32/sys_time.c +++ b/erts/emulator/sys/win32/sys_time.c @@ -63,6 +63,8 @@  static SysHrTime wrap = 0;  static DWORD last_tick_count = 0; +static erts_smp_mtx_t wrap_lock; +static ULONGLONG (WINAPI *pGetTickCount64)(void) = NULL;  /* Getting timezone information is a heavy operation, so we want to do this      only once */ @@ -77,11 +79,23 @@ static int days_in_month[2][13] = {  int   sys_init_time(void)  { +    char kernel_dll_name[] = "kernel32"; +    HMODULE module; + +    module = GetModuleHandle(kernel_dll_name); +    pGetTickCount64 = (module != NULL) ?  +	(ULONGLONG (WINAPI *)(void))  +	GetProcAddress(module,"GetTickCount64") :  +	NULL; +      if(GetTimeZoneInformation(&static_tzi) &&          static_tzi.StandardDate.wMonth != 0 &&         static_tzi.DaylightDate.wMonth != 0) {  	have_static_tzi = 1;      } + +    erts_smp_mtx_init(&wrap_lock, "sys_gethrtime"); +      return 1;  } @@ -363,15 +377,39 @@ sys_gettimeofday(SysTimeval *tv)  			 EPOCH_JULIAN_DIFF);  } +extern int erts_initialized;  SysHrTime   sys_gethrtime(void)   { -    DWORD ticks = (SysHrTime) (GetTickCount() & 0x7FFFFFFF); -    if (ticks < (SysHrTime) last_tick_count) { -	wrap += LL_LITERAL(1) << 31; +    if (pGetTickCount64 != NULL) { +	return ((SysHrTime) pGetTickCount64()) * LL_LITERAL(1000000); +    } else { +	DWORD ticks; +	SysHrTime res; +	erts_smp_mtx_lock(&wrap_lock); +	ticks = (SysHrTime) (GetTickCount() & 0x7FFFFFFF); +	if (ticks < (SysHrTime) last_tick_count) { +	    /* Detect a race that should no longer be here... */ +	    if ((((SysHrTime) last_tick_count) - ((SysHrTime) ticks)) > 1000) { +		wrap += LL_LITERAL(1) << 31; +	    } else { +		/*  +		 * XXX Debug: Violates locking order, remove all this, +		 * after testing! +		 */ +		erts_dsprintf_buf_t *dsbufp = erts_create_logger_dsbuf(); +		erts_dsprintf(dsbufp, "Did not wrap when last_tick %d " +			      "and tick %d",  +			      last_tick_count, ticks); +		erts_send_error_to_logger_nogl(dsbufp); +		ticks = last_tick_count; +	    } +	} +	last_tick_count = ticks; +	res = ((((LONGLONG) ticks) + wrap) * LL_LITERAL(1000000)); +	erts_smp_mtx_unlock(&wrap_lock); +	return res;      } -    last_tick_count = ticks; -    return ((((LONGLONG) ticks) + wrap) * LL_LITERAL(1000000));  }  clock_t  diff --git a/erts/emulator/test/trace_SUITE.erl b/erts/emulator/test/trace_SUITE.erl index 221b65309a..caa58ae281 100644 --- a/erts/emulator/test/trace_SUITE.erl +++ b/erts/emulator/test/trace_SUITE.erl @@ -34,6 +34,7 @@  	 system_monitor_args/1, more_system_monitor_args/1,  	 system_monitor_long_gc_1/1, system_monitor_long_gc_2/1,   	 system_monitor_large_heap_1/1, system_monitor_large_heap_2/1, +	 system_monitor_long_schedule/1,  	 bad_flag/1, trace_delivered/1]).  -include_lib("test_server/include/test_server.hrl"). @@ -52,6 +53,7 @@ all() ->       set_on_first_spawn, system_monitor_args,       more_system_monitor_args, system_monitor_long_gc_1,       system_monitor_long_gc_2, system_monitor_large_heap_1, +      system_monitor_long_schedule,       system_monitor_large_heap_2, bad_flag, trace_delivered].  groups() ->  @@ -508,6 +510,65 @@ try_l(Val) ->      ?line {Self,Comb1} = erlang:system_monitor(undefined),      ?line [{large_heap,Val},{long_gc,Arbitrary2}] = lists:sort(Comb1). +monitor_sys(Parent) -> +    receive  +	{monitor,Pid,long_schedule,Data} when is_pid(Pid) ->  +	    io:format("Long schedule of ~w: ~w~n",[Pid,Data]), +	    Parent ! {Pid,Data}, +	    monitor_sys(Parent); +	{monitor,Port,long_schedule,Data} when is_port(Port) ->  +	    {name,Name} = erlang:port_info(Port,name), +	    io:format("Long schedule of ~w (~p): ~w~n",[Port,Name,Data]), +	    Parent ! {Port,Data}, +	    monitor_sys(Parent); +	Other -> +	    erlang:display(Other) +    end. + +start_monitor() -> +    Parent = self(), +    Mpid = spawn_link(fun() -> monitor_sys(Parent) end), +    erlang:system_monitor(Mpid,[{long_schedule,100}]), +    erlang:yield(), % Need to be rescheduled for the trace to take +    ok. + +system_monitor_long_schedule(suite) -> +    []; +system_monitor_long_schedule(doc) -> +    ["Tests erlang:system_monitor(Pid, [{long_schedule,Time}])"]; +system_monitor_long_schedule(Config) when is_list(Config) -> +    Path = ?config(data_dir, Config), +    erl_ddll:start(), +    case (catch load_driver(Path, slow_drv)) of +	ok -> +	    do_system_monitor_long_schedule(); +	_Error -> +	    {skip, "Unable to load slow_drv (windows or no usleep()?)"} +    end. +do_system_monitor_long_schedule() -> +    start_monitor(), +    Port = open_port({spawn_driver,slow_drv}, []), +    "ok" = erlang:port_control(Port,0,[]), +    Self = self(), +    receive +	{Self,L} when is_list(L) -> +	    ok +    after 1000 -> +	    ?t:fail(no_trace_of_pid) +    end, +    "ok" = erlang:port_control(Port,1,[]), +    "ok" = erlang:port_control(Port,2,[]), +    receive +	{Port,LL} when is_list(LL) -> +	    ok +    after 1000 -> +	    ?t:fail(no_trace_of_port) +    end, +    port_close(Port), +    erlang:system_monitor(undefined), +    ok. + +  -define(LONG_GC_SLEEP, 670).  system_monitor_long_gc_1(suite) -> @@ -1521,3 +1582,11 @@ issue_non_empty_runq_warning(DeadLine, RQLen) ->  	      "         Processes info: ~p~n",  	      [DeadLine div 1000, RQLen, self(), PIs]),      receive after 1000 -> ok end. + +load_driver(Dir, Driver) -> +    case erl_ddll:load_driver(Dir, Driver) of +	ok -> ok; +	{error, Error} = Res -> +	    io:format("~s\n", [erl_ddll:format_error(Error)]), +	    Res +    end. diff --git a/erts/preloaded/ebin/erlang.beam b/erts/preloaded/ebin/erlang.beam Binary files differindex 947fbe7c24..d977b237be 100644 --- a/erts/preloaded/ebin/erlang.beam +++ b/erts/preloaded/ebin/erlang.beam diff --git a/erts/preloaded/src/erlang.erl b/erts/preloaded/src/erlang.erl index a8a4425b63..34785ff2a9 100644 --- a/erts/preloaded/src/erlang.erl +++ b/erts/preloaded/src/erlang.erl @@ -188,6 +188,7 @@        'busy_port' |        'busy_dist_port' |        {'long_gc', non_neg_integer()} | +      {'long_schedule', non_neg_integer()} |        {'large_heap', non_neg_integer()}. @@ -3553,9 +3554,7 @@ sched_wall_time(Ref, N, Acc) ->  	{Ref, SWT} -> sched_wall_time(Ref, N-1, [SWT|Acc])      end. --spec erlang:gather_gc_info_result(Ref) -> [{pos_integer(), -					     pos_integer(), -					     0}] when +-spec erlang:gather_gc_info_result(Ref) -> [{number(),number(),0}] when        Ref :: reference().  gather_gc_info_result(Ref) when erlang:is_reference(Ref) -> diff --git a/lib/common_test/src/ct_run.erl b/lib/common_test/src/ct_run.erl index 41d53c7b43..266ca73417 100644 --- a/lib/common_test/src/ct_run.erl +++ b/lib/common_test/src/ct_run.erl @@ -402,7 +402,8 @@ script_start2(Opts = #opts{vts = undefined,  	    Relaxed = get_start_opt(allow_user_terms, true, false, Args),  	    case catch ct_testspec:collect_tests_from_file(Specs1, Relaxed) of  		{E,Reason} when E == error ; E == 'EXIT' -> -		    {error,Reason}; +		    StackTrace = erlang:get_stacktrace(), +		    {error,{invalid_testspec,{Reason,StackTrace}}};  		TestSpecData ->  		    execute_all_specs(TestSpecData, Opts, Args, [])  	    end; @@ -1101,7 +1102,8 @@ run_spec_file(Relaxed,      AbsSpecs1 = get_start_opt(join_specs, [AbsSpecs], AbsSpecs, StartOpts),      case catch ct_testspec:collect_tests_from_file(AbsSpecs1, Relaxed) of  	{Error,CTReason} when Error == error ; Error == 'EXIT' -> -	    exit({error,CTReason}); +	    StackTrace = erlang:get_stacktrace(), +	    exit({error,{invalid_testspec,{CTReason,StackTrace}}});  	TestSpecData ->  	    run_all_specs(TestSpecData, Opts, StartOpts, [])      end. diff --git a/lib/common_test/src/ct_testspec.erl b/lib/common_test/src/ct_testspec.erl index 71b03c0ea6..c07ea323e6 100644 --- a/lib/common_test/src/ct_testspec.erl +++ b/lib/common_test/src/ct_testspec.erl @@ -253,7 +253,7 @@ collect_tests_from_file(Specs,Nodes,Relaxed) when is_list(Nodes) ->      Specs2 = [filename:absname(S) || S <- Specs1],      TS0 = #testspec{nodes=NodeRefs}, -    try create_specs(Specs2,TS0,Relaxed,Join) of +    try create_testspecs(Specs2,TS0,Relaxed,Join) of  	{{[],_},SeparateTestSpecs} ->  	    filter_and_convert(SeparateTestSpecs);  	{{_,#testspec{tests=[]}},SeparateTestSpecs} -> @@ -262,8 +262,10 @@ collect_tests_from_file(Specs,Nodes,Relaxed) when is_list(Nodes) ->  	    [filter_and_convert(Joined) |  	     filter_and_convert(SeparateTestSpecs)]      catch +	_:Error={error,_} -> +	    Error;  	_:Error -> -	    Error +	    {error,Error}      end.  filter_and_convert(Joined) when is_tuple(Joined) -> @@ -293,9 +295,12 @@ delete_dups1([E|Es],Keep) ->  delete_dups1([],Keep) ->      Keep. -create_specs(Specs,TestSpec,Relaxed,Join) -> -    SpecsTree = create_spec_tree(Specs,TestSpec,Join,[]),    -    create_specs(SpecsTree,TestSpec,Relaxed). +create_testspecs(Specs,TestSpec,Relaxed,Join) -> +    %% SpecsTree = {SpecAbsName, TermsInSpec, +    %%              IncludedJoinTree, IncludedSeparateTree, +    %%              JoinSpecWithRest, RestSpecsTree} +    SpecsTree = create_spec_tree(Specs,TestSpec,Join,[]), +    create_specs(SpecsTree,TestSpec,TestSpec,Relaxed).  create_spec_tree([Spec|Specs],TS,JoinWithNext,Known) ->      SpecDir = filename:dirname(filename:absname(Spec)), @@ -325,27 +330,31 @@ create_spec_tree([],_TS,_JoinWithNext,_Known) ->      [].  create_specs({Spec,Terms,InclJoin,InclSep,JoinWithNext,NextSpec}, -	     TestSpec,Relaxed) -> +	     TestSpec,TestSpec0,Relaxed) ->      SpecDir = filename:dirname(filename:absname(Spec)),      TestSpec1 = create_spec(Terms,TestSpec#testspec{spec_dir=SpecDir},  			    JoinWithNext,Relaxed), -    {{JoinSpecs1,JoinTS1},Separate1} = create_specs(InclJoin,TestSpec1,Relaxed), +    {{JoinSpecs1,JoinTS1},Separate1} = create_specs(InclJoin,TestSpec1, +						    TestSpec0,Relaxed),      {{JoinSpecs2,JoinTS2},Separate2} =  	case JoinWithNext of  	    true -> -		create_specs(NextSpec,JoinTS1,Relaxed); +		create_specs(NextSpec,JoinTS1, +			     TestSpec0,Relaxed);  	    false ->  		{{[],JoinTS1},[]}  	end, -    {SepJoinSpecs,Separate3} = create_specs(InclSep,TestSpec,Relaxed), +    {SepJoinSpecs,Separate3} = create_specs(InclSep,TestSpec0, +					    TestSpec0,Relaxed),      {SepJoinSpecs1,Separate4} =  	case JoinWithNext of  	    true ->  		{{[],TestSpec},[]};  	    false -> -		create_specs(NextSpec,TestSpec,Relaxed) +		create_specs(NextSpec,TestSpec0, +			     TestSpec0,Relaxed)  	end,                  SpecInfo = {Spec,TestSpec1#testspec.merge_tests}, @@ -354,7 +363,6 @@ create_specs({Spec,Terms,InclJoin,InclSep,JoinWithNext,NextSpec},  		                        [SepJoinSpecs]++Separate2++  		                        [SepJoinSpecs1]++Separate4,  		   Ss /= []], -      case {JoinWithNext,JoinSpecs1} of  	{true,_} ->  	    {{[SpecInfo|(JoinSpecs1++JoinSpecs2)],JoinTS2}, @@ -366,7 +374,7 @@ create_specs({Spec,Terms,InclJoin,InclSep,JoinWithNext,NextSpec},  	    {{[SpecInfo|(JoinSpecs1++JoinSpecs2)],JoinTS2},  	     AllSeparate}      end; -create_specs([],TestSpec,_Relaxed) -> +create_specs([],TestSpec,_,_Relaxed) ->      {{[],TestSpec},[]}.  create_spec(Terms,TestSpec,JoinedByPrev,Relaxed) -> @@ -842,7 +850,8 @@ add_tests([{cases,Node,Dir,Suite,Cs}|Ts],Spec) ->      Tests = Spec#testspec.tests,      Tests1 = insert_cases(ref2node(Node,Spec#testspec.nodes),  			  ref2dir(Dir,Spec), -			  Suite,Cs,Tests, Spec#testspec.merge_tests), +			  Suite,Cs,Tests, +			  Spec#testspec.merge_tests),      add_tests(Ts,Spec#testspec{tests=Tests1});  %% --- skip_suites --- @@ -1246,17 +1255,22 @@ insert_cases(Node,Dir,Suite,Cases,Tests,false) when is_list(Cases) ->      append({{Node,Dir},[{Suite,Cases}]},Tests);  insert_cases(Node,Dir,Suite,Cases,Tests,true) when is_list(Cases) ->      {Tests1,Done} = -	lists:foldr(fun(All={{N,D},[{all,_}]},{Replaced,_}) when N == Node, +	lists:foldr(fun(All={{N,D},[{all,_}]},{Merged,_}) when N == Node,  								 D == Dir -> -			    {[All|Replaced],true}; -		       ({{N,D},Suites0},{Replaced,_}) when N == Node, +			    {[All|Merged],true}; +		       ({{N,D},Suites0},{Merged,_}) when N == Node,  							   D == Dir ->  			    Suites1 = insert_cases1(Suite,Cases,Suites0), -			    {[{{N,D},Suites1}|Replaced],true}; -		       (T,{Replaced,Match}) -> -			    {[T|Replaced],Match} +			    {[{{N,D},Suites1}|Merged],true}; +		       (T,{Merged,Match}) -> +			    {[T|Merged],Match}  		    end, {[],false}, Tests), -    if not Done -> +    if Tests == [] -> +	    %% initial case with length(Cases) > 1, we need to do this +	    %% to merge possible duplicate cases in Cases +	    [{{Node,Dir},insert_cases1(Suite,Cases,[{Suite,[]}])}]; +	not Done -> +	    %% no merging done, simply add these cases to Tests   	    Tests ++ [{{Node,Dir},[{Suite,Cases}]}];         true ->  	    Tests1 @@ -1301,14 +1315,14 @@ skip_groups(Node,Dir,Suite,Groups,Cases,Cmt,Tests,false) when  skip_groups(Node,Dir,Suite,Groups,Cases,Cmt,Tests,true) when        ((Cases == all) or is_list(Cases)) and is_list(Groups) ->      {Tests1,Done} = -	lists:foldr(fun({{N,D},Suites0},{Replaced,_}) when N == Node, +	lists:foldr(fun({{N,D},Suites0},{Merged,_}) when N == Node,  							   D == Dir ->  			    Suites1 = skip_groups1(Suite,  						   [{Gr,Cases} || Gr <- Groups],  						   Cmt,Suites0), -			    {[{{N,D},Suites1}|Replaced],true}; -		       (T,{Replaced,Match}) -> -			    {[T|Replaced],Match} +			    {[{{N,D},Suites1}|Merged],true}; +		       (T,{Merged,Match}) -> +			    {[T|Merged],Match}  		    end, {[],false}, Tests),      if not Done ->  	    Tests ++ [{{Node,Dir},skip_groups1(Suite, @@ -1339,12 +1353,12 @@ skip_cases(Node,Dir,Suite,Cases,Cmt,Tests,false) when is_list(Cases) ->      append({{Node,Dir},Suites1},Tests);  skip_cases(Node,Dir,Suite,Cases,Cmt,Tests,true) when is_list(Cases) ->      {Tests1,Done} = -	lists:foldr(fun({{N,D},Suites0},{Replaced,_}) when N == Node, +	lists:foldr(fun({{N,D},Suites0},{Merged,_}) when N == Node,  							   D == Dir ->  			    Suites1 = skip_cases1(Suite,Cases,Cmt,Suites0), -			    {[{{N,D},Suites1}|Replaced],true}; -		       (T,{Replaced,Match}) -> -			    {[T|Replaced],Match} +			    {[{{N,D},Suites1}|Merged],true}; +		       (T,{Merged,Match}) -> +			    {[T|Merged],Match}  		    end, {[],false}, Tests),      if not Done ->  	    Tests ++ [{{Node,Dir},skip_cases1(Suite,Cases,Cmt,[])}]; diff --git a/lib/common_test/test/ct_testspec_3_SUITE.erl b/lib/common_test/test/ct_testspec_3_SUITE.erl index 6b4b729552..5fa187e5b4 100644 --- a/lib/common_test/test/ct_testspec_3_SUITE.erl +++ b/lib/common_test/test/ct_testspec_3_SUITE.erl @@ -284,6 +284,24 @@ events_to_check(_, 0) ->  events_to_check(Test, N) ->      test_events(Test) ++ events_to_check(Test, N-1). + +%%%! +%%%! IMPORTANT NOTE ABOUT THE TEST ORDER: +%%%! +%%%! When merging testspec terms, CT will group the tests by TestDir and +%%%! Suite, before term order (in testspec). That means that if tests +%%%! are ordered like e.g: +%%%!   {Dir1,Suite11}, {Dir2,Suite21}, {Dir1,Suite12}, +%%%! the execution order after merge (even if no merge takes place), +%%%! will be: +%%%!   {Dir1,[Suite11,Suite12]}, {Dir2,Suite21} +%%%! +%%%! Also, tests in a tree of included testspecs are always collected +%%%! and merged in depth-first manner, meaning even if a particular test is +%%%! on a higher level in the tree, it may be executed later than a test on a +%%%! lower level. +%%%! +  test_events(start_separate) ->      [{?eh,start_logging,{'DEF','RUNDIR'}},       {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}}, @@ -300,6 +318,7 @@ test_events(start_separate) ->       {?eh,tc_done,{t21_SUITE,end_per_suite,ok}},       {?eh,test_done,{'DEF','STOP_TIME'}},       {?eh,stop_logging,[]}, +       {?eh,start_logging,{'DEF','RUNDIR'}},       {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}},       {?eh,start_info,{3,2,15}}, @@ -415,6 +434,7 @@ test_events(incl_separate1) ->       {?eh,test_done,{'DEF','STOP_TIME'}},       {?eh,stop_logging,[]}, +       {?eh,start_logging,{'DEF','RUNDIR'}},       {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}},       {?eh,start_info,{3,2,15}}, @@ -448,6 +468,7 @@ test_events(incl_separate2) ->       {?eh,tc_done,{t22_SUITE,end_per_suite,ok}},       {?eh,test_done,{'DEF','STOP_TIME'}},       {?eh,stop_logging,[]}, +       {?eh,start_logging,{'DEF','RUNDIR'}},       {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}},       {?eh,start_info,{3,2,15}}, @@ -468,6 +489,7 @@ test_events(incl_separate2) ->       {?eh,tc_done,{t22_SUITE,end_per_suite,ok}},       {?eh,test_done,{'DEF','STOP_TIME'}},       {?eh,stop_logging,[]}, +       {?eh,start_logging,{'DEF','RUNDIR'}},       {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}},       {?eh,start_info,{2,2,10}}, @@ -483,6 +505,7 @@ test_events(incl_separate2) ->       {?eh,tc_done,{t21_SUITE,end_per_suite,ok}},       {?eh,test_done,{'DEF','STOP_TIME'}},       {?eh,stop_logging,[]}, +       {?eh,start_logging,{'DEF','RUNDIR'}},       {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}},       {?eh,start_info,{2,2,10}}, @@ -498,6 +521,7 @@ test_events(incl_separate2) ->       {?eh,tc_done,{t21_SUITE,end_per_suite,ok}},       {?eh,test_done,{'DEF','STOP_TIME'}},       {?eh,stop_logging,[]}, +       {?eh,start_logging,{'DEF','RUNDIR'}},       {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}},       {?eh,start_info,{3,2,15}}, @@ -545,6 +569,7 @@ test_events(incl_join1) ->       {?eh,tc_done,{t21_SUITE,end_per_suite,ok}},       {?eh,test_done,{'DEF','STOP_TIME'}},       {?eh,stop_logging,[]}, +       {?eh,start_logging,{'DEF','RUNDIR'}},       {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}},       {?eh,start_info,{4,4,20}}, @@ -614,6 +639,7 @@ test_events(incl_both1) ->       {?eh,tc_done,{t21_SUITE,end_per_suite,ok}},       {?eh,test_done,{'DEF','STOP_TIME'}},       {?eh,stop_logging,[]}, +       {?eh,start_logging,{'DEF','RUNDIR'}},       {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}},       {?eh,start_info,{3,2,15}}, @@ -634,6 +660,7 @@ test_events(incl_both1) ->       {?eh,tc_done,{t22_SUITE,end_per_suite,ok}},       {?eh,test_done,{'DEF','STOP_TIME'}},       {?eh,stop_logging,[]}, +       {?eh,start_logging,{'DEF','RUNDIR'}},       {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}},       {?eh,start_info,{2,2,10}}, @@ -649,6 +676,7 @@ test_events(incl_both1) ->       {?eh,tc_done,{t22_SUITE,end_per_suite,ok}},       {?eh,test_done,{'DEF','STOP_TIME'}},       {?eh,stop_logging,[]}, +       {?eh,start_logging,{'DEF','RUNDIR'}},       {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}},       {?eh,start_info,{2,2,10}}, @@ -692,6 +720,7 @@ test_events(incl_both2) ->       {?eh,tc_done,{t22_SUITE,end_per_suite,ok}},       {?eh,test_done,{'DEF','STOP_TIME'}},       {?eh,stop_logging,[]}, +       {?eh,start_logging,{'DEF','RUNDIR'}},       {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}},       {?eh,start_info,{3,2,15}}, @@ -712,6 +741,7 @@ test_events(incl_both2) ->       {?eh,tc_done,{t22_SUITE,end_per_suite,ok}},       {?eh,test_done,{'DEF','STOP_TIME'}},       {?eh,stop_logging,[]}, +       {?eh,start_logging,{'DEF','RUNDIR'}},       {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}},       {?eh,start_info,{2,2,10}}, @@ -728,18 +758,890 @@ test_events(incl_both2) ->       {?eh,test_done,{'DEF','STOP_TIME'}},       {?eh,stop_logging,[]}]; -test_events(incl_both_and_join1) -> []; -test_events(incl_both_and_join2) -> []; -test_events(rec_incl_separate1) -> []; -test_events(rec_incl_separate2) -> []; -test_events(rec_incl_join1) -> []; -test_events(rec_incl_join2) -> []; -test_events(rec_incl_separate_join1) -> []; -test_events(rec_incl_separate_join2) -> []; -test_events(rec_incl_join_separate1) -> []; -test_events(rec_incl_join_separate2) -> []; - -test_events(_) -> -    []. +test_events(incl_both_and_join1) ->  +    [ +     {?eh,start_logging,{'DEF','RUNDIR'}}, +     {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}}, +     {?eh,start_info,{5,3,25}}, +     {?eh,tc_start,{t11_SUITE,init_per_suite}}, +     {?eh,tc_done,{t11_SUITE,init_per_suite,ok}}, +     {?eh,test_stats,{1,2,{1,1}}}, +     {?eh,tc_start,{t11_SUITE,end_per_suite}}, +     {?eh,tc_done,{t11_SUITE,end_per_suite,ok}}, +     {?eh,tc_start,{t21_SUITE,init_per_suite}}, +     {?eh,tc_done,{t21_SUITE,init_per_suite,ok}}, +     {?eh,test_stats,{2,4,{2,2}}}, +     {?eh,tc_start,{t21_SUITE,end_per_suite}}, +     {?eh,tc_done,{t21_SUITE,end_per_suite,ok}}, +     {?eh,tc_start,{t11_SUITE,init_per_suite}}, +     {?eh,tc_done,{t11_SUITE,init_per_suite,ok}}, +     {?eh,test_stats,{3,6,{3,3}}}, +     {?eh,tc_start,{t11_SUITE,end_per_suite}}, +     {?eh,tc_done,{t11_SUITE,end_per_suite,ok}}, +     {?eh,tc_start,{t11_SUITE,init_per_suite}}, +     {?eh,tc_done,{t11_SUITE,init_per_suite,ok}}, +     {?eh,test_stats,{4,8,{4,4}}}, +     {?eh,tc_start,{t11_SUITE,end_per_suite}}, +     {?eh,tc_done,{t11_SUITE,end_per_suite,ok}}, +     {?eh,tc_start,{t21_SUITE,init_per_suite}}, +     {?eh,tc_done,{t21_SUITE,init_per_suite,ok}}, +     {?eh,test_stats,{5,10,{5,5}}}, +     {?eh,tc_start,{t21_SUITE,end_per_suite}}, +     {?eh,tc_done,{t21_SUITE,end_per_suite,ok}}, +     {?eh,test_done,{'DEF','STOP_TIME'}}, +     {?eh,stop_logging,[]}, + +     {?eh,start_logging,{'DEF','RUNDIR'}}, +     {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}}, +     {?eh,start_info,{3,2,15}}, +     {?eh,tc_start,{t12_SUITE,init_per_suite}}, +     {?eh,tc_done,{t12_SUITE,init_per_suite,ok}}, +     {?eh,test_stats,{1,2,{1,1}}}, +     {?eh,tc_start,{t12_SUITE,end_per_suite}}, +     {?eh,tc_done,{t12_SUITE,end_per_suite,ok}}, +     {?eh,tc_start,{t12_SUITE,init_per_suite}}, +     {?eh,tc_done,{t12_SUITE,init_per_suite,ok}}, +     {?eh,test_stats,{2,4,{2,2}}}, +     {?eh,tc_start,{t12_SUITE,end_per_suite}}, +     {?eh,tc_done,{t12_SUITE,end_per_suite,ok}}, +     {?eh,tc_start,{t22_SUITE,init_per_suite}}, +     {?eh,tc_done,{t22_SUITE,init_per_suite,ok}}, +     {?eh,test_stats,{3,6,{3,3}}}, +     {?eh,tc_start,{t22_SUITE,end_per_suite}}, +     {?eh,tc_done,{t22_SUITE,end_per_suite,ok}}, +     {?eh,test_done,{'DEF','STOP_TIME'}}, +     {?eh,stop_logging,[]}, + +     {?eh,start_logging,{'DEF','RUNDIR'}}, +     {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}}, +     {?eh,start_info,{4,4,20}}, +     {?eh,tc_start,{t11_SUITE,init_per_suite}}, +     {?eh,tc_done,{t11_SUITE,init_per_suite,ok}}, +     {?eh,test_stats,{1,2,{1,1}}}, +     {?eh,tc_start,{t11_SUITE,end_per_suite}}, +     {?eh,tc_done,{t11_SUITE,end_per_suite,ok}}, +     {?eh,tc_start,{t12_SUITE,init_per_suite}}, +     {?eh,tc_done,{t12_SUITE,init_per_suite,ok}}, +     {?eh,test_stats,{2,4,{2,2}}}, +     {?eh,tc_start,{t12_SUITE,end_per_suite}}, +     {?eh,tc_done,{t12_SUITE,end_per_suite,ok}}, +     {?eh,tc_start,{t21_SUITE,init_per_suite}}, +     {?eh,tc_done,{t21_SUITE,init_per_suite,ok}}, +     {?eh,test_stats,{3,6,{3,3}}}, +     {?eh,tc_start,{t21_SUITE,end_per_suite}}, +     {?eh,tc_done,{t21_SUITE,end_per_suite,ok}}, +     {?eh,tc_start,{t22_SUITE,init_per_suite}}, +     {?eh,tc_done,{t22_SUITE,init_per_suite,ok}}, +     {?eh,test_stats,{4,8,{4,4}}}, +     {?eh,tc_start,{t22_SUITE,end_per_suite}}, +     {?eh,tc_done,{t22_SUITE,end_per_suite,ok}}, +     {?eh,test_done,{'DEF','STOP_TIME'}}, +     {?eh,stop_logging,[]}, + +     {?eh,start_logging,{'DEF','RUNDIR'}}, +     {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}}, +     {?eh,start_info,{2,2,10}}, +     {?eh,tc_start,{t11_SUITE,init_per_suite}}, +     {?eh,tc_done,{t11_SUITE,init_per_suite,ok}}, +     {?eh,test_stats,{1,2,{1,1}}}, +     {?eh,tc_start,{t11_SUITE,end_per_suite}}, +     {?eh,tc_done,{t11_SUITE,end_per_suite,ok}}, +     {?eh,tc_start,{t21_SUITE,init_per_suite}}, +     {?eh,tc_done,{t21_SUITE,init_per_suite,ok}}, +     {?eh,test_stats,{2,4,{2,2}}}, +     {?eh,tc_start,{t21_SUITE,end_per_suite}}, +     {?eh,tc_done,{t21_SUITE,end_per_suite,ok}}, +     {?eh,test_done,{'DEF','STOP_TIME'}}, +     {?eh,stop_logging,[]}]; + +test_events(incl_both_and_join2) -> +    [ +     {?eh,start_logging,{'DEF','RUNDIR'}}, +     {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}}, +     {?eh,start_info,{4,4,20}}, +     {?eh,tc_start,{t11_SUITE,init_per_suite}}, +     {?eh,tc_done,{t11_SUITE,init_per_suite,ok}}, +     {?eh,test_stats,{1,2,{1,1}}}, +     {?eh,tc_start,{t11_SUITE,end_per_suite}}, +     {?eh,tc_done,{t11_SUITE,end_per_suite,ok}}, +     {?eh,tc_start,{t12_SUITE,init_per_suite}}, +     {?eh,tc_done,{t12_SUITE,init_per_suite,ok}}, +     {?eh,test_stats,{2,4,{2,2}}}, +     {?eh,tc_start,{t12_SUITE,end_per_suite}}, +     {?eh,tc_done,{t12_SUITE,end_per_suite,ok}}, +     {?eh,tc_start,{t21_SUITE,init_per_suite}}, +     {?eh,tc_done,{t21_SUITE,init_per_suite,ok}}, +     {?eh,test_stats,{3,6,{3,3}}}, +     {?eh,tc_start,{t21_SUITE,end_per_suite}}, +     {?eh,tc_done,{t21_SUITE,end_per_suite,ok}}, +     {?eh,tc_start,{t22_SUITE,init_per_suite}}, +     {?eh,tc_done,{t22_SUITE,init_per_suite,ok}}, +     {?eh,test_stats,{4,8,{4,4}}}, +     {?eh,tc_start,{t22_SUITE,end_per_suite}}, +     {?eh,tc_done,{t22_SUITE,end_per_suite,ok}}, +     {?eh,test_done,{'DEF','STOP_TIME'}}, +     {?eh,stop_logging,[]}, + +     {?eh,start_logging,{'DEF','RUNDIR'}}, +     {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}}, +     {?eh,start_info,{3,2,15}}, +     {?eh,tc_start,{t12_SUITE,init_per_suite}}, +     {?eh,tc_done,{t12_SUITE,init_per_suite,ok}}, +     {?eh,test_stats,{1,2,{1,1}}}, +     {?eh,tc_start,{t12_SUITE,end_per_suite}}, +     {?eh,tc_done,{t12_SUITE,end_per_suite,ok}}, +     {?eh,tc_start,{t12_SUITE,init_per_suite}}, +     {?eh,tc_done,{t12_SUITE,init_per_suite,ok}}, +     {?eh,test_stats,{2,4,{2,2}}}, +     {?eh,tc_start,{t12_SUITE,end_per_suite}}, +     {?eh,tc_done,{t12_SUITE,end_per_suite,ok}}, +     {?eh,tc_start,{t22_SUITE,init_per_suite}}, +     {?eh,tc_done,{t22_SUITE,init_per_suite,ok}}, +     {?eh,test_stats,{3,6,{3,3}}}, +     {?eh,tc_start,{t22_SUITE,end_per_suite}}, +     {?eh,tc_done,{t22_SUITE,end_per_suite,ok}}, +     {?eh,test_done,{'DEF','STOP_TIME'}}, +     {?eh,stop_logging,[]}, + +     {?eh,start_logging,{'DEF','RUNDIR'}}, +     {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}}, +     {?eh,start_info,{2,2,10}}, +     {?eh,tc_start,{t11_SUITE,init_per_suite}}, +     {?eh,tc_done,{t11_SUITE,init_per_suite,ok}}, +     {?eh,test_stats,{1,2,{1,1}}}, +     {?eh,tc_start,{t11_SUITE,end_per_suite}}, +     {?eh,tc_done,{t11_SUITE,end_per_suite,ok}}, +     {?eh,tc_start,{t21_SUITE,init_per_suite}}, +     {?eh,tc_done,{t21_SUITE,init_per_suite,ok}}, +     {?eh,test_stats,{2,4,{2,2}}}, +     {?eh,tc_start,{t21_SUITE,end_per_suite}}, +     {?eh,tc_done,{t21_SUITE,end_per_suite,ok}}, +     {?eh,test_done,{'DEF','STOP_TIME'}}, +     {?eh,stop_logging,[]}]; + +test_events(rec_incl_separate1) -> +    [ +     {?eh,start_logging,{'DEF','RUNDIR'}}, +     {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}}]  +	++ flat_spec2_events() ++ +    [{?eh,test_done,{'DEF','STOP_TIME'}}, +     {?eh,stop_logging,[]}, + +     {?eh,start_logging,{'DEF','RUNDIR'}}, +     {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}}] +	++ flat_spec1_events() ++ +    [{?eh,test_done,{'DEF','STOP_TIME'}}, +     {?eh,stop_logging,[]}, + +     {?eh,start_logging,{'DEF','RUNDIR'}}, +     {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}}, +     {?eh,start_info,{1,1,5}}, +     {?eh,tc_start,{t22_SUITE,init_per_suite}}, +     {?eh,tc_done,{t22_SUITE,init_per_suite,ok}}, +     {?eh,test_stats,{1,2,{1,1}}}, +     {?eh,tc_start,{t22_SUITE,end_per_suite}}, +     {?eh,tc_done,{t22_SUITE,end_per_suite,ok}}, +     {?eh,test_done,{'DEF','STOP_TIME'}}, +     {?eh,stop_logging,[]}, +      +     {?eh,start_logging,{'DEF','RUNDIR'}}, +     {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}}] +	++ flat_spec1_events() ++ +    [{?eh,test_done,{'DEF','STOP_TIME'}}, +     {?eh,stop_logging,[]}, + +     {?eh,start_logging,{'DEF','RUNDIR'}}, +     {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}}] +	++ flat_spec2_events() ++ +    [{?eh,test_done,{'DEF','STOP_TIME'}}, +     {?eh,stop_logging,[]}, + +     {?eh,start_logging,{'DEF','RUNDIR'}}, +     {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}}, +     {?eh,start_info,{1,1,5}}, +     {?eh,tc_start,{t23_SUITE,init_per_suite}}, +     {?eh,tc_done,{t23_SUITE,init_per_suite,ok}}, +     {?eh,test_stats,{1,2,{1,1}}}, +     {?eh,tc_start,{t23_SUITE,end_per_suite}}, +     {?eh,tc_done,{t23_SUITE,end_per_suite,ok}}, +     {?eh,test_done,{'DEF','STOP_TIME'}}, +     {?eh,stop_logging,[]}, + +     {?eh,start_logging,{'DEF','RUNDIR'}}, +     {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}}, +     {?eh,start_info,{1,1,5}}, +     {?eh,tc_start,{t22_SUITE,init_per_suite}}, +     {?eh,tc_done,{t22_SUITE,init_per_suite,ok}}, +     {?eh,tc_start,{t22_SUITE,end_per_suite}}, +     {?eh,tc_done,{t22_SUITE,end_per_suite,ok}}, +     {?eh,test_done,{'DEF','STOP_TIME'}}, +     {?eh,stop_logging,[]}, + +     {?eh,start_logging,{'DEF','RUNDIR'}}, +     {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}}] +	++ flat_spec1_events() ++ +    [{?eh,test_done,{'DEF','STOP_TIME'}}, +     {?eh,stop_logging,[]}, + +     {?eh,start_logging,{'DEF','RUNDIR'}}, +     {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}}] +	++ flat_spec2_events() ++ +    [{?eh,test_done,{'DEF','STOP_TIME'}}, +     {?eh,stop_logging,[]}, + +     {?eh,start_logging,{'DEF','RUNDIR'}}, +     {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}}] +	++ flat_spec2_events() ++ +    [{?eh,test_done,{'DEF','STOP_TIME'}}, +     {?eh,stop_logging,[]}, + +     {?eh,start_logging,{'DEF','RUNDIR'}}, +     {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}}] +	++ flat_spec1_events() ++ +    [{?eh,test_done,{'DEF','STOP_TIME'}}, +     {?eh,stop_logging,[]}]; + +test_events(rec_incl_separate2) -> +    [ +     {?eh,start_logging,{'DEF','RUNDIR'}}, +     {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}}, +     {?eh,start_info,{1,1,5}}, +     {?eh,tc_start,{t23_SUITE,init_per_suite}}, +     {?eh,tc_done,{t23_SUITE,init_per_suite,ok}}, +     {?eh,test_stats,{1,2,{1,1}}}, +     {?eh,tc_start,{t23_SUITE,end_per_suite}}, +     {?eh,tc_done,{t23_SUITE,end_per_suite,ok}}, +     {?eh,test_done,{'DEF','STOP_TIME'}}, +     {?eh,stop_logging,[]}, + +     {?eh,start_logging,{'DEF','RUNDIR'}}, +     {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}}] +	++ flat_spec2_events() ++	 +    [{?eh,test_done,{'DEF','STOP_TIME'}}, +     {?eh,stop_logging,[]}, +	  +     {?eh,start_logging,{'DEF','RUNDIR'}}, +     {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}}] +	++ flat_spec1_events() ++ +    [{?eh,test_done,{'DEF','STOP_TIME'}}, +     {?eh,stop_logging,[]}, + +     {?eh,start_logging,{'DEF','RUNDIR'}}, +     {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}}, +     {?eh,start_info,{1,1,5}}, +     {?eh,tc_start,{t22_SUITE,init_per_suite}}, +     {?eh,tc_done,{t22_SUITE,init_per_suite,ok}}, +     {?eh,test_stats,{1,2,{1,1}}}, +     {?eh,tc_start,{t22_SUITE,end_per_suite}}, +     {?eh,tc_done,{t22_SUITE,end_per_suite,ok}}, +     {?eh,test_done,{'DEF','STOP_TIME'}}, +     {?eh,stop_logging,[]}, + +     {?eh,start_logging,{'DEF','RUNDIR'}}, +     {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}}] +	++ flat_spec1_events() ++ +    [{?eh,test_done,{'DEF','STOP_TIME'}}, +     {?eh,stop_logging,[]}, + +     {?eh,start_logging,{'DEF','RUNDIR'}}, +     {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}}] +	++ flat_spec2_events() ++ +    [{?eh,test_done,{'DEF','STOP_TIME'}}, +     {?eh,stop_logging,[]}, + +     {?eh,start_logging,{'DEF','RUNDIR'}}, +     {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}}, +     {?eh,start_info,{1,1,5}}, +     {?eh,tc_start,{t22_SUITE,init_per_suite}}, +     {?eh,tc_done,{t22_SUITE,init_per_suite,ok}}, +     {?eh,test_stats,{1,2,{1,1}}}, +     {?eh,tc_start,{t22_SUITE,end_per_suite}}, +     {?eh,tc_done,{t22_SUITE,end_per_suite,ok}}, +     {?eh,test_done,{'DEF','STOP_TIME'}}, +     {?eh,stop_logging,[]}, + +     {?eh,start_logging,{'DEF','RUNDIR'}}, +     {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}}] +	++ flat_spec1_events() ++ +    [{?eh,test_done,{'DEF','STOP_TIME'}}, +     {?eh,stop_logging,[]}, + +     {?eh,start_logging,{'DEF','RUNDIR'}}, +     {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}}] +	++ flat_spec2_events() ++ +    [{?eh,test_done,{'DEF','STOP_TIME'}}, +     {?eh,stop_logging,[]}, + +     {?eh,start_logging,{'DEF','RUNDIR'}}, +     {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}}] +	++ flat_spec2_events() ++ +    [{?eh,test_done,{'DEF','STOP_TIME'}}, +     {?eh,stop_logging,[]}, +	 +     {?eh,start_logging,{'DEF','RUNDIR'}}, +     {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}}] +	++ flat_spec1_events() ++ +    [{?eh,test_done,{'DEF','STOP_TIME'}}, +     {?eh,stop_logging,[]}]; + +test_events(rec_incl_join1) -> +    [ +     {?eh,start_logging,{'DEF','RUNDIR'}}, +     {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}}, +     {?eh,start_info,{4,4,20}}, +     {?eh,tc_start,{t12_SUITE,init_per_suite}}, +     {?eh,tc_done,{t12_SUITE,init_per_suite,ok}}, +     {?eh,tc_start,{t12_SUITE,end_per_suite}}, +     {?eh,tc_done,{t12_SUITE,end_per_suite,ok}}, +     {?eh,tc_start,{t11_SUITE,init_per_suite}}, +     {?eh,tc_done,{t11_SUITE,init_per_suite,ok}}, +     {?eh,tc_start,{t11_SUITE,end_per_suite}}, +     {?eh,tc_done,{t11_SUITE,end_per_suite,ok}}, +     {?eh,tc_start,{t22_SUITE,init_per_suite}}, +     {?eh,tc_done,{t22_SUITE,init_per_suite,ok}}, +     {?eh,tc_start,{t22_SUITE,end_per_suite}}, +     {?eh,tc_done,{t22_SUITE,end_per_suite,ok}}, +     {?eh,tc_start,{t21_SUITE,init_per_suite}}, +     {?eh,tc_done,{t21_SUITE,init_per_suite,ok}}, +     {?eh,test_stats,{4,8,{4,4}}}, +     {?eh,tc_start,{t21_SUITE,end_per_suite}}, +     {?eh,tc_done,{t21_SUITE,end_per_suite,ok}}, +     {?eh,test_done,{'DEF','STOP_TIME'}}, +     {?eh,stop_logging,[]}, +     {?eh,start_logging,{'DEF','RUNDIR'}}, +     {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}}, +     {?eh,start_info,{5,5,25}}, +     {?eh,tc_start,{t23_SUITE,init_per_suite}}, +     {?eh,tc_done,{t23_SUITE,init_per_suite,ok}}, +     {?eh,tc_start,{t23_SUITE,end_per_suite}}, +     {?eh,tc_done,{t23_SUITE,end_per_suite,ok}}, +     {?eh,tc_start,{t22_SUITE,init_per_suite}}, +     {?eh,tc_done,{t22_SUITE,init_per_suite,ok}}, +     {?eh,tc_start,{t22_SUITE,end_per_suite}}, +     {?eh,tc_done,{t22_SUITE,end_per_suite,ok}}, +     {?eh,tc_start,{t21_SUITE,init_per_suite}}, +     {?eh,tc_done,{t21_SUITE,init_per_suite,ok}}, +     {?eh,tc_start,{t21_SUITE,end_per_suite}}, +     {?eh,tc_done,{t21_SUITE,end_per_suite,ok}}, +     {?eh,tc_start,{t11_SUITE,init_per_suite}}, +     {?eh,tc_done,{t11_SUITE,init_per_suite,ok}}, +     {?eh,tc_start,{t11_SUITE,end_per_suite}}, +     {?eh,tc_done,{t11_SUITE,end_per_suite,ok}}, +     {?eh,tc_start,{t12_SUITE,init_per_suite}}, +     {?eh,tc_done,{t12_SUITE,init_per_suite,ok}}, +     {?eh,test_stats,{5,10,{5,5}}}, +     {?eh,tc_start,{t12_SUITE,end_per_suite}}, +     {?eh,tc_done,{t12_SUITE,end_per_suite,ok}}, +     {?eh,test_done,{'DEF','STOP_TIME'}}, +     {?eh,stop_logging,[]}]; +test_events(rec_incl_join2) -> +    [ +     {?eh,start_logging,{'DEF','RUNDIR'}}, +     {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}}, +     {?eh,start_info,{5,5,25}}, +     {?eh,tc_start,{t12_SUITE,init_per_suite}}, +     {?eh,tc_done,{t12_SUITE,init_per_suite,ok}}, +     {?eh,tc_start,{t12_SUITE,end_per_suite}}, +     {?eh,tc_done,{t12_SUITE,end_per_suite,ok}}, +     {?eh,tc_start,{t11_SUITE,init_per_suite}}, +     {?eh,tc_done,{t11_SUITE,init_per_suite,ok}}, +     {?eh,tc_start,{t11_SUITE,end_per_suite}}, +     {?eh,tc_done,{t11_SUITE,end_per_suite,ok}}, +     {?eh,tc_start,{t22_SUITE,init_per_suite}}, +     {?eh,tc_done,{t22_SUITE,init_per_suite,ok}}, +     {?eh,tc_start,{t22_SUITE,end_per_suite}}, +     {?eh,tc_done,{t22_SUITE,end_per_suite,ok}}, +     {?eh,tc_start,{t21_SUITE,init_per_suite}}, +     {?eh,tc_done,{t21_SUITE,init_per_suite,ok}}, +     {?eh,tc_start,{t21_SUITE,end_per_suite}}, +     {?eh,tc_done,{t21_SUITE,end_per_suite,ok}}, +     {?eh,tc_start,{t23_SUITE,init_per_suite}}, +     {?eh,tc_done,{t23_SUITE,init_per_suite,ok}}, +     {?eh,test_stats,{5,10,{5,5}}}, +     {?eh,tc_start,{t23_SUITE,end_per_suite}}, +     {?eh,tc_done,{t23_SUITE,end_per_suite,ok}}, +     {?eh,test_done,{'DEF','STOP_TIME'}}, +     {?eh,stop_logging,[]}]; + +test_events(rec_incl_separate_join1) -> +    [ +     {?eh,start_logging,{'DEF','RUNDIR'}}, +     {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}}, +     {?eh,start_info,{4,4,20}}, +     {?eh,tc_start,{t22_SUITE,init_per_suite}}, +     {?eh,tc_done,{t22_SUITE,init_per_suite,ok}}, +     {?eh,tc_start,{t22_SUITE,end_per_suite}}, +     {?eh,tc_done,{t22_SUITE,end_per_suite,ok}}, +     {?eh,tc_start,{t21_SUITE,init_per_suite}}, +     {?eh,tc_done,{t21_SUITE,init_per_suite,ok}}, +     {?eh,tc_start,{t21_SUITE,end_per_suite}}, +     {?eh,tc_done,{t21_SUITE,end_per_suite,ok}}, +     {?eh,tc_start,{t11_SUITE,init_per_suite}}, +     {?eh,tc_done,{t11_SUITE,init_per_suite,ok}}, +     {?eh,tc_start,{t11_SUITE,end_per_suite}}, +     {?eh,tc_done,{t11_SUITE,end_per_suite,ok}}, +     {?eh,tc_start,{t12_SUITE,init_per_suite}}, +     {?eh,tc_done,{t12_SUITE,init_per_suite,ok}}, +     {?eh,test_stats,{4,8,{4,4}}}, +     {?eh,tc_start,{t12_SUITE,end_per_suite}}, +     {?eh,tc_done,{t12_SUITE,end_per_suite,ok}}, +     {?eh,test_done,{'DEF','STOP_TIME'}}, +     {?eh,stop_logging,[]}, + +     {?eh,start_logging,{'DEF','RUNDIR'}}, +     {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}}, +     {?eh,start_info,{4,4,20}}, +     {?eh,tc_start,{t12_SUITE,init_per_suite}}, +     {?eh,tc_done,{t12_SUITE,init_per_suite,ok}}, +     {?eh,tc_start,{t12_SUITE,end_per_suite}}, +     {?eh,tc_done,{t12_SUITE,end_per_suite,ok}}, +     {?eh,tc_start,{t11_SUITE,init_per_suite}}, +     {?eh,tc_done,{t11_SUITE,init_per_suite,ok}}, +     {?eh,tc_start,{t11_SUITE,end_per_suite}}, +     {?eh,tc_done,{t11_SUITE,end_per_suite,ok}}, +     {?eh,tc_start,{t22_SUITE,init_per_suite}}, +     {?eh,tc_done,{t22_SUITE,init_per_suite,ok}}, +     {?eh,tc_start,{t22_SUITE,end_per_suite}}, +     {?eh,tc_done,{t22_SUITE,end_per_suite,ok}}, +     {?eh,tc_start,{t21_SUITE,init_per_suite}}, +     {?eh,tc_done,{t21_SUITE,init_per_suite,ok}}, +     {?eh,test_stats,{4,8,{4,4}}}, +     {?eh,tc_start,{t21_SUITE,end_per_suite}}, +     {?eh,tc_done,{t21_SUITE,end_per_suite,ok}}, +     {?eh,test_done,{'DEF','STOP_TIME'}}, +     {?eh,stop_logging,[]}, + +     {?eh,start_logging,{'DEF','RUNDIR'}}, +     {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}}, +     {?eh,start_info,{1,1,5}}, +     {?eh,tc_start,{t23_SUITE,init_per_suite}}, +     {?eh,tc_done,{t23_SUITE,init_per_suite,ok}}, +     {?eh,test_stats,{1,2,{1,1}}}, +     {?eh,tc_start,{t23_SUITE,end_per_suite}}, +     {?eh,tc_done,{t23_SUITE,end_per_suite,ok}}, +     {?eh,test_done,{'DEF','STOP_TIME'}}, +     {?eh,stop_logging,[]}, + +     {?eh,start_logging,{'DEF','RUNDIR'}}, +     {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}}, +     {?eh,start_info,{4,4,20}}, +     {?eh,tc_start,{t12_SUITE,init_per_suite}}, +     {?eh,tc_done,{t12_SUITE,init_per_suite,ok}}, +     {?eh,tc_start,{t12_SUITE,end_per_suite}}, +     {?eh,tc_done,{t12_SUITE,end_per_suite,ok}}, +     {?eh,tc_start,{t11_SUITE,init_per_suite}}, +     {?eh,tc_done,{t11_SUITE,init_per_suite,ok}}, +     {?eh,tc_start,{t11_SUITE,end_per_suite}}, +     {?eh,tc_done,{t11_SUITE,end_per_suite,ok}}, +     {?eh,tc_start,{t22_SUITE,init_per_suite}}, +     {?eh,tc_done,{t22_SUITE,init_per_suite,ok}}, +     {?eh,tc_start,{t22_SUITE,end_per_suite}}, +     {?eh,tc_done,{t22_SUITE,end_per_suite,ok}}, +     {?eh,tc_start,{t21_SUITE,init_per_suite}}, +     {?eh,tc_done,{t21_SUITE,init_per_suite,ok}}, +     {?eh,test_stats,{4,8,{4,4}}}, +     {?eh,tc_start,{t21_SUITE,end_per_suite}}, +     {?eh,tc_done,{t21_SUITE,end_per_suite,ok}}, +     {?eh,test_done,{'DEF','STOP_TIME'}}, +     {?eh,stop_logging,[]}, + +     {?eh,start_logging,{'DEF','RUNDIR'}}, +     {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}}, +     {?eh,start_info,{4,4,20}}, +     {?eh,tc_start,{t22_SUITE,init_per_suite}}, +     {?eh,tc_done,{t22_SUITE,init_per_suite,ok}}, +     {?eh,tc_start,{t22_SUITE,end_per_suite}}, +     {?eh,tc_done,{t22_SUITE,end_per_suite,ok}}, +     {?eh,tc_start,{t21_SUITE,init_per_suite}}, +     {?eh,tc_done,{t21_SUITE,init_per_suite,ok}}, +     {?eh,tc_start,{t21_SUITE,end_per_suite}}, +     {?eh,tc_done,{t21_SUITE,end_per_suite,ok}}, +     {?eh,tc_start,{t11_SUITE,init_per_suite}}, +     {?eh,tc_done,{t11_SUITE,init_per_suite,ok}}, +     {?eh,tc_start,{t11_SUITE,end_per_suite}}, +     {?eh,tc_done,{t11_SUITE,end_per_suite,ok}}, +     {?eh,tc_start,{t12_SUITE,init_per_suite}}, +     {?eh,tc_done,{t12_SUITE,init_per_suite,ok}}, +     {?eh,test_stats,{4,8,{4,4}}}, +     {?eh,tc_start,{t12_SUITE,end_per_suite}}, +     {?eh,tc_done,{t12_SUITE,end_per_suite,ok}}, +     {?eh,test_done,{'DEF','STOP_TIME'}}, +     {?eh,stop_logging,[]}]; + +test_events(rec_incl_separate_join2) -> +    [ +     {?eh,start_logging,{'DEF','RUNDIR'}}, +     {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}}, +     {?eh,start_info,{1,1,5}}, +     {?eh,tc_start,{t23_SUITE,init_per_suite}}, +     {?eh,tc_done,{t23_SUITE,init_per_suite,ok}}, +     {?eh,test_stats,{1,2,{1,1}}}, +     {?eh,tc_start,{t23_SUITE,end_per_suite}}, +     {?eh,tc_done,{t23_SUITE,end_per_suite,ok}}, +     {?eh,test_done,{'DEF','STOP_TIME'}}, +     {?eh,stop_logging,[]}, + +     {?eh,start_logging,{'DEF','RUNDIR'}}, +     {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}}, +     {?eh,start_info,{4,4,20}}, +     {?eh,tc_start,{t22_SUITE,init_per_suite}}, +     {?eh,tc_done,{t22_SUITE,init_per_suite,ok}}, +     {?eh,tc_start,{t22_SUITE,end_per_suite}}, +     {?eh,tc_done,{t22_SUITE,end_per_suite,ok}}, +     {?eh,tc_start,{t21_SUITE,init_per_suite}}, +     {?eh,tc_done,{t21_SUITE,init_per_suite,ok}}, +     {?eh,tc_start,{t21_SUITE,end_per_suite}}, +     {?eh,tc_done,{t21_SUITE,end_per_suite,ok}}, +     {?eh,tc_start,{t11_SUITE,init_per_suite}}, +     {?eh,tc_done,{t11_SUITE,init_per_suite,ok}}, +     {?eh,tc_start,{t11_SUITE,end_per_suite}}, +     {?eh,tc_done,{t11_SUITE,end_per_suite,ok}}, +     {?eh,tc_start,{t12_SUITE,init_per_suite}}, +     {?eh,tc_done,{t12_SUITE,init_per_suite,ok}}, +     {?eh,test_stats,{4,8,{4,4}}}, +     {?eh,tc_start,{t12_SUITE,end_per_suite}}, +     {?eh,tc_done,{t12_SUITE,end_per_suite,ok}}, +     {?eh,test_done,{'DEF','STOP_TIME'}}, +     {?eh,stop_logging,[]}, + +     {?eh,start_logging,{'DEF','RUNDIR'}}, +     {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}}, +     {?eh,start_info,{4,4,20}}, +     {?eh,tc_start,{t12_SUITE,init_per_suite}}, +     {?eh,tc_done,{t12_SUITE,init_per_suite,ok}}, +     {?eh,tc_start,{t12_SUITE,end_per_suite}}, +     {?eh,tc_done,{t12_SUITE,end_per_suite,ok}}, +     {?eh,tc_start,{t11_SUITE,init_per_suite}}, +     {?eh,tc_done,{t11_SUITE,init_per_suite,ok}}, +     {?eh,tc_start,{t11_SUITE,end_per_suite}}, +     {?eh,tc_done,{t11_SUITE,end_per_suite,ok}}, +     {?eh,tc_start,{t22_SUITE,init_per_suite}}, +     {?eh,tc_done,{t22_SUITE,init_per_suite,ok}}, +     {?eh,tc_start,{t22_SUITE,end_per_suite}}, +     {?eh,tc_done,{t22_SUITE,end_per_suite,ok}}, +     {?eh,tc_start,{t21_SUITE,init_per_suite}}, +     {?eh,tc_done,{t21_SUITE,init_per_suite,ok}}, +     {?eh,test_stats,{4,8,{4,4}}}, +     {?eh,tc_start,{t21_SUITE,end_per_suite}}, +     {?eh,tc_done,{t21_SUITE,end_per_suite,ok}}, +     {?eh,test_done,{'DEF','STOP_TIME'}}, + +     {?eh,stop_logging,[]}, +     {?eh,start_logging,{'DEF','RUNDIR'}}, +     {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}}, +     {?eh,start_info,{4,4,20}}, +     {?eh,tc_start,{t12_SUITE,init_per_suite}}, +     {?eh,tc_done,{t12_SUITE,init_per_suite,ok}}, +     {?eh,tc_start,{t12_SUITE,end_per_suite}}, +     {?eh,tc_done,{t12_SUITE,end_per_suite,ok}}, +     {?eh,tc_start,{t11_SUITE,init_per_suite}}, +     {?eh,tc_done,{t11_SUITE,init_per_suite,ok}}, +     {?eh,tc_start,{t11_SUITE,end_per_suite}}, +     {?eh,tc_done,{t11_SUITE,end_per_suite,ok}}, +     {?eh,tc_start,{t22_SUITE,init_per_suite}}, +     {?eh,tc_done,{t22_SUITE,init_per_suite,ok}}, +     {?eh,tc_start,{t22_SUITE,end_per_suite}}, +     {?eh,tc_done,{t22_SUITE,end_per_suite,ok}}, +     {?eh,tc_start,{t21_SUITE,init_per_suite}}, +     {?eh,tc_done,{t21_SUITE,init_per_suite,ok}}, +     {?eh,test_stats,{4,8,{4,4}}}, +     {?eh,tc_start,{t21_SUITE,end_per_suite}}, +     {?eh,tc_done,{t21_SUITE,end_per_suite,ok}}, +     {?eh,test_done,{'DEF','STOP_TIME'}}, +     {?eh,stop_logging,[]}, + +     {?eh,start_logging,{'DEF','RUNDIR'}}, +     {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}}, +     {?eh,start_info,{4,4,20}}, +     {?eh,tc_start,{t22_SUITE,init_per_suite}}, +     {?eh,tc_done,{t22_SUITE,init_per_suite,ok}}, +     {?eh,tc_start,{t22_SUITE,end_per_suite}}, +     {?eh,tc_done,{t22_SUITE,end_per_suite,ok}}, +     {?eh,tc_start,{t21_SUITE,init_per_suite}}, +     {?eh,tc_done,{t21_SUITE,init_per_suite,ok}}, +     {?eh,tc_start,{t21_SUITE,end_per_suite}}, +     {?eh,tc_done,{t21_SUITE,end_per_suite,ok}}, +     {?eh,tc_start,{t11_SUITE,init_per_suite}}, +     {?eh,tc_done,{t11_SUITE,init_per_suite,ok}}, +     {?eh,tc_start,{t11_SUITE,end_per_suite}}, +     {?eh,tc_done,{t11_SUITE,end_per_suite,ok}}, +     {?eh,tc_start,{t12_SUITE,init_per_suite}}, +     {?eh,tc_done,{t12_SUITE,init_per_suite,ok}}, +     {?eh,test_stats,{4,8,{4,4}}}, +     {?eh,tc_start,{t12_SUITE,end_per_suite}}, +     {?eh,tc_done,{t12_SUITE,end_per_suite,ok}}, +     {?eh,test_done,{'DEF','STOP_TIME'}}, +     {?eh,stop_logging,[]}]; + +test_events(rec_incl_join_separate1) -> +    [{?eh,start_logging,{'DEF','RUNDIR'}}, +     {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}}, +     {?eh,start_info,{2,2,10}}, +     {?eh,tc_start,{t23_SUITE,init_per_suite}}, +     {?eh,tc_done,{t23_SUITE,init_per_suite,ok}}, +     {?eh,test_stats,{1,2,{1,1}}}, +     {?eh,tc_start,{t23_SUITE,end_per_suite}}, +     {?eh,tc_done,{t23_SUITE,end_per_suite,ok}}, +     {?eh,tc_start,{t22_SUITE,init_per_suite}}, +     {?eh,tc_done,{t22_SUITE,init_per_suite,ok}}, +     {?eh,test_stats,{2,4,{2,2}}}, +     {?eh,tc_start,{t22_SUITE,end_per_suite}}, +     {?eh,tc_done,{t22_SUITE,end_per_suite,ok}}, +     {?eh,test_done,{'DEF','STOP_TIME'}}, +     {?eh,stop_logging,[]}, + +     {?eh,start_logging,{'DEF','RUNDIR'}}, +     {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}}] +	++ flat_spec2_events() ++ +    [{?eh,test_done,{'DEF','STOP_TIME'}}, +     {?eh,stop_logging,[]}, + +     {?eh,start_logging,{'DEF','RUNDIR'}}, +     {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}}] +	++ flat_spec1_events() ++ +    [{?eh,test_done,{'DEF','STOP_TIME'}}, +     {?eh,stop_logging,[]}, + +     {?eh,start_logging,{'DEF','RUNDIR'}}, +     {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}}] +	++ flat_spec1_events() ++ +    [{?eh,test_done,{'DEF','STOP_TIME'}}, +     {?eh,stop_logging,[]}, + +     {?eh,start_logging,{'DEF','RUNDIR'}}, +     {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}}] +	++ flat_spec2_events() ++ +    [{?eh,test_done,{'DEF','STOP_TIME'}}, +     {?eh,stop_logging,[]}, + +     {?eh,start_logging,{'DEF','RUNDIR'}}, +     {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}}, +     {?eh,start_info,{2,2,10}}, +     {?eh,tc_start,{t23_SUITE,init_per_suite}}, +     {?eh,tc_done,{t23_SUITE,init_per_suite,ok}}, +     {?eh,test_stats,{1,2,{1,1}}}, +     {?eh,tc_start,{t23_SUITE,end_per_suite}}, +     {?eh,tc_done,{t23_SUITE,end_per_suite,ok}}, +     {?eh,tc_start,{t22_SUITE,init_per_suite}}, +     {?eh,tc_done,{t22_SUITE,init_per_suite,ok}}, +     {?eh,test_stats,{2,4,{2,2}}}, +     {?eh,tc_start,{t22_SUITE,end_per_suite}}, +     {?eh,tc_done,{t22_SUITE,end_per_suite,ok}}, +     {?eh,test_done,{'DEF','STOP_TIME'}}, +     {?eh,stop_logging,[]}, + +     {?eh,start_logging,{'DEF','RUNDIR'}}, +     {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}}] +	++ flat_spec1_events() ++ +    [{?eh,test_done,{'DEF','STOP_TIME'}}, +     {?eh,stop_logging,[]}, + +     {?eh,start_logging,{'DEF','RUNDIR'}}, +     {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}}] +	++ flat_spec2_events() ++ + +    [{?eh,test_done,{'DEF','STOP_TIME'}}, +     {?eh,stop_logging,[]}, +     {?eh,start_logging,{'DEF','RUNDIR'}}, +     {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}}] +	++ flat_spec2_events() ++ +    [{?eh,test_done,{'DEF','STOP_TIME'}}, +     {?eh,stop_logging,[]}, + +     {?eh,start_logging,{'DEF','RUNDIR'}}, +     {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}}] +	++ flat_spec1_events() ++ +    [{?eh,test_done,{'DEF','STOP_TIME'}}, +     {?eh,stop_logging,[]}]; + +test_events(rec_incl_join_separate2) -> +    [{?eh,start_logging,{'DEF','RUNDIR'}}, +     {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}}, +     {?eh,start_info,{2,2,10}}, +     {?eh,tc_start,{t23_SUITE,init_per_suite}}, +     {?eh,tc_done,{t23_SUITE,init_per_suite,ok}}, +     {?eh,test_stats,{1,2,{1,1}}}, +     {?eh,tc_start,{t23_SUITE,end_per_suite}}, +     {?eh,tc_done,{t23_SUITE,end_per_suite,ok}}, +     {?eh,tc_start,{t22_SUITE,init_per_suite}}, +     {?eh,tc_done,{t22_SUITE,init_per_suite,ok}}, +     {?eh,test_stats,{2,4,{2,2}}}, +     {?eh,tc_start,{t22_SUITE,end_per_suite}}, +     {?eh,tc_done,{t22_SUITE,end_per_suite,ok}}, +     {?eh,test_done,{'DEF','STOP_TIME'}}, +     {?eh,stop_logging,[]}, + +     {?eh,start_logging,{'DEF','RUNDIR'}}, +     {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}}] +	++ flat_spec2_events() ++ +    [{?eh,test_done,{'DEF','STOP_TIME'}}, +     {?eh,stop_logging,[]}, + +     {?eh,start_logging,{'DEF','RUNDIR'}}, +     {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}}] +	++ flat_spec1_events() ++ +    [{?eh,test_done,{'DEF','STOP_TIME'}}, +     {?eh,stop_logging,[]}, + +     {?eh,start_logging,{'DEF','RUNDIR'}}, +     {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}}] +	++ flat_spec1_events() ++ +    [{?eh,test_done,{'DEF','STOP_TIME'}}, +     {?eh,stop_logging,[]}, + +     {?eh,start_logging,{'DEF','RUNDIR'}}, +     {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}}] +	++ flat_spec2_events() ++ +    [{?eh,test_done,{'DEF','STOP_TIME'}}, +     {?eh,stop_logging,[]}, + +     {?eh,start_logging,{'DEF','RUNDIR'}}, +     {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}}] +	++ flat_spec1_events() ++ +    [{?eh,test_done,{'DEF','STOP_TIME'}}, +     {?eh,stop_logging,[]}, + +     {?eh,start_logging,{'DEF','RUNDIR'}}, +     {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}}] +	++ flat_spec2_events() ++ +    [{?eh,test_done,{'DEF','STOP_TIME'}}, +     {?eh,stop_logging,[]}, + +     {?eh,start_logging,{'DEF','RUNDIR'}}, +     {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}}] +	++ flat_spec2_events() ++ +    [{?eh,test_done,{'DEF','STOP_TIME'}}, +     {?eh,stop_logging,[]}, + +     {?eh,start_logging,{'DEF','RUNDIR'}}, +     {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}}] +	++ flat_spec1_events() ++ +    [{?eh,test_done,{'DEF','STOP_TIME'}}, +     {?eh,stop_logging,[]}]. + +%%%----------------------------------------------------------------- + +flat_spec1_events() -> +    [ +     {?eh,start_info,{2,2,10}}, +     {?eh,tc_start,{t11_SUITE,init_per_suite}}, +     {?eh,tc_done,{t11_SUITE,init_per_suite,ok}}, +     {?eh,tc_start,{t11_SUITE,ok_tc}}, +     {?eh,tc_done,{t11_SUITE,ok_tc,ok}}, +     {?eh,test_stats,{1,0,{0,0}}}, +     {?eh,tc_start,{t11_SUITE,exit_tc}}, +     {?eh,tc_done,{t11_SUITE,exit_tc,{failed,{error,kaboom}}}}, +     {?eh,test_stats,{1,1,{0,0}}}, +     {?eh,tc_start,{t11_SUITE,to_tc}}, +     {?eh,tc_done,{t11_SUITE,to_tc,{failed,{timetrap_timeout,1}}}}, +     {?eh,test_stats,{1,2,{0,0}}}, +     {?eh,tc_start,{t11_SUITE,autoskip_tc}}, +     {?eh,tc_done, +      {t11_SUITE,autoskip_tc,{skipped, +			      {failed, +			       {t11_SUITE,init_per_testcase, +				{kaboom,'_'}}}}}}, +     {?eh,test_stats,{1,2,{0,1}}}, +     {?eh,tc_start,{t11_SUITE,userskip_tc}}, +     {?eh,tc_done,{t11_SUITE,userskip_tc,{skipped,"user skipped"}}}, +     {?eh,test_stats,{1,2,{1,1}}}, +     {?eh,tc_start,{t11_SUITE,end_per_suite}}, +     {?eh,tc_done,{t11_SUITE,end_per_suite,ok}}, +     {?eh,tc_start,{t21_SUITE,init_per_suite}}, +     {?eh,tc_done,{t21_SUITE,init_per_suite,ok}}, +     {?eh,tc_start,{t21_SUITE,ok_tc}}, +     {?eh,tc_done,{t21_SUITE,ok_tc,ok}}, +     {?eh,test_stats,{2,2,{1,1}}}, +     {?eh,tc_start,{t21_SUITE,exit_tc}}, +     {?eh,tc_done,{t21_SUITE,exit_tc,{failed,{error,kaboom}}}}, +     {?eh,test_stats,{2,3,{1,1}}}, +     {?eh,tc_start,{t21_SUITE,to_tc}}, +     {?eh,tc_done,{t21_SUITE,to_tc,{failed,{timetrap_timeout,1}}}}, +     {?eh,test_stats,{2,4,{1,1}}}, +     {?eh,tc_start,{t21_SUITE,autoskip_tc}}, +     {?eh,tc_done, +      {t21_SUITE,autoskip_tc,{skipped, +			      {failed, +			       {t21_SUITE,init_per_testcase, +				{kaboom,'_'}}}}}}, +     {?eh,test_stats,{2,4,{1,2}}}, +     {?eh,tc_start,{t21_SUITE,userskip_tc}}, +     {?eh,tc_done,{t21_SUITE,userskip_tc,{skipped,"user skipped"}}}, +     {?eh,test_stats,{2,4,{2,2}}}, +     {?eh,tc_start,{t21_SUITE,end_per_suite}}, +     {?eh,tc_done,{t21_SUITE,end_per_suite,ok}}]. + +flat_spec2_events() -> +    [ +     {?eh,start_info,{3,2,15}}, +     {?eh,tc_start,{t12_SUITE,init_per_suite}}, +     {?eh,tc_done,{t12_SUITE,init_per_suite,ok}}, +     {?eh,tc_start,{t12_SUITE,ok_tc}}, +     {?eh,tc_done,{t12_SUITE,ok_tc,ok}}, +     {?eh,test_stats,{1,0,{0,0}}}, +     {?eh,tc_start,{t12_SUITE,exit_tc}}, +     {?eh,tc_done,{t12_SUITE,exit_tc,{failed,{error,kaboom}}}}, +     {?eh,test_stats,{1,1,{0,0}}}, +     {?eh,tc_start,{t12_SUITE,to_tc}}, +     {?eh,tc_done,{t12_SUITE,to_tc,{failed,{timetrap_timeout,1}}}}, +     {?eh,test_stats,{1,2,{0,0}}}, +     {?eh,tc_start,{t12_SUITE,autoskip_tc}}, +     {?eh,tc_done, +      {t12_SUITE,autoskip_tc,{skipped, +			      {failed, +			       {t12_SUITE,init_per_testcase, +				{kaboom,'_'}}}}}}, +     {?eh,test_stats,{1,2,{0,1}}}, +     {?eh,tc_start,{t12_SUITE,userskip_tc}}, +     {?eh,tc_done,{t12_SUITE,userskip_tc,{skipped,"user skipped"}}}, +     {?eh,test_stats,{1,2,{1,1}}}, +     {?eh,tc_start,{t12_SUITE,end_per_suite}}, +     {?eh,tc_done,{t12_SUITE,end_per_suite,ok}}, +     {?eh,tc_start,{t12_SUITE,init_per_suite}}, +     {?eh,tc_done,{t12_SUITE,init_per_suite,ok}}, +     {?eh,tc_start,{t12_SUITE,ok_tc}}, +     {?eh,tc_done,{t12_SUITE,ok_tc,ok}}, +     {?eh,test_stats,{2,2,{1,1}}}, +     {?eh,tc_start,{t12_SUITE,exit_tc}}, +     {?eh,tc_done,{t12_SUITE,exit_tc,{failed,{error,kaboom}}}}, +     {?eh,test_stats,{2,3,{1,1}}}, +     {?eh,tc_start,{t12_SUITE,to_tc}}, +     {?eh,tc_done,{t12_SUITE,to_tc,{failed,{timetrap_timeout,1}}}}, +     {?eh,test_stats,{2,4,{1,1}}}, +     {?eh,tc_start,{t12_SUITE,autoskip_tc}}, +     {?eh,tc_done, +      {t12_SUITE,autoskip_tc,{skipped, +			      {failed, +			       {t12_SUITE,init_per_testcase, +				{kaboom,'_'}}}}}}, +     {?eh,test_stats,{2,4,{1,2}}}, +     {?eh,tc_start,{t12_SUITE,userskip_tc}}, +     {?eh,tc_done,{t12_SUITE,userskip_tc,{skipped,"user skipped"}}}, +     {?eh,test_stats,{2,4,{2,2}}}, +     {?eh,tc_start,{t12_SUITE,end_per_suite}}, +     {?eh,tc_done,{t12_SUITE,end_per_suite,ok}}, +     {?eh,tc_start,{t22_SUITE,init_per_suite}}, +     {?eh,tc_done,{t22_SUITE,init_per_suite,ok}}, +     {?eh,tc_start,{t22_SUITE,ok_tc}}, +     {?eh,tc_done,{t22_SUITE,ok_tc,ok}}, +     {?eh,test_stats,{3,4,{2,2}}}, +     {?eh,tc_start,{t22_SUITE,exit_tc}}, +     {?eh,tc_done,{t22_SUITE,exit_tc,{failed,{error,kaboom}}}}, +     {?eh,test_stats,{3,5,{2,2}}}, +     {?eh,tc_start,{t22_SUITE,to_tc}}, +     {?eh,tc_done,{t22_SUITE,to_tc,{failed,{timetrap_timeout,1}}}}, +     {?eh,test_stats,{3,6,{2,2}}}, +     {?eh,tc_start,{t22_SUITE,autoskip_tc}}, +     {?eh,tc_done, +      {t22_SUITE,autoskip_tc,{skipped, +			      {failed, +			       {t22_SUITE,init_per_testcase, +				{kaboom,'_'}}}}}}, +     {?eh,test_stats,{3,6,{2,3}}}, +     {?eh,tc_start,{t22_SUITE,userskip_tc}}, +     {?eh,tc_done,{t22_SUITE,userskip_tc,{skipped,"user skipped"}}}, +     {?eh,test_stats,{3,6,{3,3}}}, +     {?eh,tc_start,{t22_SUITE,end_per_suite}}, +     {?eh,tc_done,{t22_SUITE,end_per_suite,ok}}]. diff --git a/lib/stdlib/src/erl_lint.erl b/lib/stdlib/src/erl_lint.erl index 68a8534f15..08b8541014 100644 --- a/lib/stdlib/src/erl_lint.erl +++ b/lib/stdlib/src/erl_lint.erl @@ -522,8 +522,7 @@ start(File, Opts) ->            warn_format = value_option(warn_format, 1, warn_format, 1,  				     nowarn_format, 0, Opts),  	  enabled_warnings = Enabled, -          file = File, -	  types = default_types() +          file = File           }.  %% is_warn_enabled(Category, St) -> boolean(). @@ -1007,7 +1006,10 @@ check_undefined_functions(#lint{called=Called0,defined=Def0}=St0) ->  check_undefined_types(#lint{usage=Usage,types=Def}=St0) ->      Used = Usage#usage.used_types,      UTAs = dict:fetch_keys(Used), -    Undef = [{TA,dict:fetch(TA, Used)} || TA <- UTAs, not dict:is_key(TA, Def)], +    Undef = [{TA,dict:fetch(TA, Used)} || +		TA <- UTAs, +		not dict:is_key(TA, Def), +		not is_default_type(TA)],      foldl(fun ({TA,L}, St) ->  		  add_error(L, {undefined_type,TA}, St)  	  end, St0, Undef). @@ -2440,7 +2442,7 @@ type_def(Attr, Line, TypeName, ProtoType, Args, St0) ->          end,      case (dict:is_key(TypePair, TypeDefs) orelse is_var_arity_type(TypeName)) of  	true -> -	    case dict:is_key(TypePair, default_types()) of +	    case is_default_type(TypePair) of  		true  ->  		    case is_newly_introduced_builtin_type(TypePair) of  			%% allow some types just for bootstrapping @@ -2488,8 +2490,8 @@ check_type({paren_type, _L, [Type]}, SeenVars, St) ->  check_type({remote_type, L, [{atom, _, Mod}, {atom, _, Name}, Args]},  	   SeenVars, #lint{module=CurrentMod} = St) ->      St1 = -	case (dict:is_key({Name, length(Args)}, default_types()) -	      orelse is_var_arity_type(Name)) of +	case is_default_type({Name, length(Args)}) +	      orelse is_var_arity_type(Name) of  	    true -> add_error(L, {imported_predefined_type, Name}, St);  	    false -> St  	end, @@ -2606,63 +2608,62 @@ is_var_arity_type(union) -> true;  is_var_arity_type(record) -> true;  is_var_arity_type(_) -> false. -default_types() -> -    DefTypes = [{any, 0}, -		{arity, 0}, -		{array, 0}, -		{atom, 0}, -		{atom, 1}, -		{binary, 0}, -		{binary, 2}, -		{bitstring, 0}, -		{bool, 0}, -		{boolean, 0}, -		{byte, 0}, -		{char, 0}, -		{dict, 0}, -		{digraph, 0}, -		{float, 0}, -		{'fun', 0}, -		{'fun', 2}, -		{function, 0}, -		{gb_set, 0}, -		{gb_tree, 0}, -		{identifier, 0}, -		{integer, 0}, -		{integer, 1}, -		{iodata, 0}, -		{iolist, 0}, -		{list, 0}, -		{list, 1}, -		{maybe_improper_list, 0}, -		{maybe_improper_list, 2}, -		{mfa, 0}, -		{module, 0}, -		{neg_integer, 0}, -		{nil, 0}, -		{no_return, 0}, -		{node, 0}, -		{non_neg_integer, 0}, -		{none, 0}, -		{nonempty_list, 0}, -		{nonempty_list, 1}, -		{nonempty_improper_list, 2}, -		{nonempty_maybe_improper_list, 0}, -		{nonempty_maybe_improper_list, 2}, -		{nonempty_string, 0}, -		{number, 0}, -		{pid, 0}, -		{port, 0}, -		{pos_integer, 0}, -		{queue, 0}, -		{range, 2}, -		{reference, 0}, -		{set, 0}, -		{string, 0}, -		{term, 0}, -		{timeout, 0}, -		{var, 1}], -    dict:from_list([{T, -1} || T <- DefTypes]). +is_default_type({any, 0}) -> true; +is_default_type({arity, 0}) -> true; +is_default_type({array, 0}) -> true; +is_default_type({atom, 0}) -> true; +is_default_type({atom, 1}) -> true; +is_default_type({binary, 0}) -> true; +is_default_type({binary, 2}) -> true; +is_default_type({bitstring, 0}) -> true; +is_default_type({bool, 0}) -> true; +is_default_type({boolean, 0}) -> true; +is_default_type({byte, 0}) -> true; +is_default_type({char, 0}) -> true; +is_default_type({dict, 0}) -> true; +is_default_type({digraph, 0}) -> true; +is_default_type({float, 0}) -> true; +is_default_type({'fun', 0}) -> true; +is_default_type({'fun', 2}) -> true; +is_default_type({function, 0}) -> true; +is_default_type({gb_set, 0}) -> true; +is_default_type({gb_tree, 0}) -> true; +is_default_type({identifier, 0}) -> true; +is_default_type({integer, 0}) -> true; +is_default_type({integer, 1}) -> true; +is_default_type({iodata, 0}) -> true; +is_default_type({iolist, 0}) -> true; +is_default_type({list, 0}) -> true; +is_default_type({list, 1}) -> true; +is_default_type({maybe_improper_list, 0}) -> true; +is_default_type({maybe_improper_list, 2}) -> true; +is_default_type({mfa, 0}) -> true; +is_default_type({module, 0}) -> true; +is_default_type({neg_integer, 0}) -> true; +is_default_type({nil, 0}) -> true; +is_default_type({no_return, 0}) -> true; +is_default_type({node, 0}) -> true; +is_default_type({non_neg_integer, 0}) -> true; +is_default_type({none, 0}) -> true; +is_default_type({nonempty_list, 0}) -> true; +is_default_type({nonempty_list, 1}) -> true; +is_default_type({nonempty_improper_list, 2}) -> true; +is_default_type({nonempty_maybe_improper_list, 0}) -> true; +is_default_type({nonempty_maybe_improper_list, 2}) -> true; +is_default_type({nonempty_string, 0}) -> true; +is_default_type({number, 0}) -> true; +is_default_type({pid, 0}) -> true; +is_default_type({port, 0}) -> true; +is_default_type({pos_integer, 0}) -> true; +is_default_type({queue, 0}) -> true; +is_default_type({range, 2}) -> true; +is_default_type({reference, 0}) -> true; +is_default_type({set, 0}) -> true; +is_default_type({string, 0}) -> true; +is_default_type({term, 0}) -> true; +is_default_type({timeout, 0}) -> true; +is_default_type({var, 1}) -> true; +is_default_type(_) -> false.  %% R13  is_newly_introduced_builtin_type({arity, 0}) -> true; @@ -2776,10 +2777,7 @@ check_unused_types(Forms, #lint{usage=Usage, types=Ts, exp_types=ExpTs}=St) ->  	    L = gb_sets:to_list(ExpTs) ++ dict:fetch_keys(D),  	    UsedTypes = gb_sets:from_list(L),  	    FoldFun = -		fun(_Type, -1, AccSt) -> -			%% Default type -			AccSt; -		   (Type, #typeinfo{line = FileLine}, AccSt) -> +		fun(Type, #typeinfo{line = FileLine}, AccSt) ->                          case loc(FileLine) of  			    {FirstFile, _} ->  				case gb_sets:is_member(Type, UsedTypes) of @@ -2801,10 +2799,7 @@ check_unused_types(Forms, #lint{usage=Usage, types=Ts, exp_types=ExpTs}=St) ->  check_local_opaque_types(St) ->      #lint{types=Ts, exp_types=ExpTs} = St,      FoldFun = -        fun(_Type, -1, AccSt) -> -                %% Default type -                AccSt; -           (_Type, #typeinfo{attr = type}, AccSt) -> +        fun(_Type, #typeinfo{attr = type}, AccSt) ->                  AccSt;             (Type, #typeinfo{attr = opaque, line = FileLine}, AccSt) ->                  case gb_sets:is_element(Type, ExpTs) of diff --git a/lib/stdlib/test/unicode_SUITE.erl b/lib/stdlib/test/unicode_SUITE.erl index 4055af2741..6106a8c444 100644 --- a/lib/stdlib/test/unicode_SUITE.erl +++ b/lib/stdlib/test/unicode_SUITE.erl @@ -29,7 +29,7 @@  	 random_lists/1,  	 roundtrips/1,  	 latin1/1, -	 exceptions/1]). +	 exceptions/1, binaries_errors/1]).  init_per_testcase(Case, Config) when is_atom(Case), is_list(Config) ->      Dog=?t:timetrap(?t:minutes(20)), @@ -44,7 +44,7 @@ suite() -> [{ct_hooks,[ts_install_cth]}].  all() ->       [utf8_illegal_sequences_bif,       utf16_illegal_sequences_bif, random_lists, roundtrips, -     latin1, exceptions]. +     latin1, exceptions, binaries_errors].  groups() ->       []. @@ -61,6 +61,149 @@ init_per_group(_GroupName, Config) ->  end_per_group(_GroupName, Config) ->      Config. +binaries_errors(Config) when is_list(Config) -> +    setlimit(10), +    ex_binaries_errors_utf8(Config), +    setlimit(default), +    ex_binaries_errors_utf8(Config), +    ex_binaries_errors_utf16_little(Config), +    ex_binaries_errors_utf16_big(Config), +    ex_binaries_errors_utf32_little(Config), +    ex_binaries_errors_utf32_big(Config). +     +ex_binaries_errors_utf8(Config) when is_list(Config) -> +    %% Original smoke test, we should not forget the original offset... +    <<_:8,_:8,RR2/binary>> = <<$a,$b,164,165,$c>>, +    {error,[],<<164,165,$c>>} = unicode:characters_to_list(RR2), +    %% Now, try with longer binary (trapping) +    BrokenPart = list_to_binary(lists:seq(128,255)), +    BrokenSz = byte_size(BrokenPart), +    [ begin +	  OKList = lists:flatten(lists:duplicate(N,lists:seq(1,255))), +	  OKBin = unicode:characters_to_binary(OKList), +	  OKLen = length(OKList), +	  %% Copy to avoid that the binary get's writable +	  PartlyBroken = binary:copy(<<OKBin/binary, BrokenPart/binary>>), +	  PBSz = byte_size(PartlyBroken), +	  {error,OKList,DeepBrokenPart} =  +	      unicode:characters_to_list(PartlyBroken), +	  BrokenPart = iolist_to_binary(DeepBrokenPart), +	  [ begin +		NewList = lists:nthtail(X, OKList), +		NewSz = byte_size(unicode:characters_to_binary(NewList)) +  +		    BrokenSz, +		Chomped = binary:part(PartlyBroken,PBSz - NewSz, NewSz), +		true = (binary:referenced_byte_size(Chomped) =:= PBSz), +		{error,NewList,DeepBrokenPart2} =   +		    unicode:characters_to_list(Chomped), +		BrokenPart = iolist_to_binary(DeepBrokenPart2) +	    end || X <- lists:seq(1,OKLen) ] +      end || N <- lists:seq(1,20) ], +    ok. + +ex_binaries_errors_utf16_little(Config) when is_list(Config) -> +    BrokenPart = << <<X:16/little>> || X <- lists:seq(16#DC00,16#DFFF) >>, +    BrokenSz = byte_size(BrokenPart), +    [ begin +	  OKList = lists:flatten(lists:duplicate(N,lists:seq(1,255))), +	  OKBin = unicode:characters_to_binary(OKList,unicode,{utf16,little}), +	  OKLen = length(OKList), +	  %% Copy to avoid that the binary get's writable +	  PartlyBroken = binary:copy(<<OKBin/binary, BrokenPart/binary>>), +	  PBSz = byte_size(PartlyBroken), +	  {error,OKList,DeepBrokenPart} =  +	      unicode:characters_to_list(PartlyBroken,{utf16,little}), +	  BrokenPart = iolist_to_binary(DeepBrokenPart), +	  [ begin +		NewList = lists:nthtail(X, OKList), +		NewSz = byte_size(unicode:characters_to_binary(NewList,unicode,{utf16,little})) +  +		    BrokenSz, +		Chomped = binary:part(PartlyBroken,PBSz - NewSz, NewSz), +		true = (binary:referenced_byte_size(Chomped) =:= PBSz), +		{error,NewList,DeepBrokenPart2} =   +		    unicode:characters_to_list(Chomped,{utf16,little}), +		BrokenPart = iolist_to_binary(DeepBrokenPart2) +	    end || X <- lists:seq(1,OKLen) ] +      end || N <- lists:seq(1,15) ], +    ok. +ex_binaries_errors_utf16_big(Config) when is_list(Config) -> +    BrokenPart = << <<X:16/big>> || X <- lists:seq(16#DC00,16#DFFF) >>, +    BrokenSz = byte_size(BrokenPart), +    [ begin +	  OKList = lists:flatten(lists:duplicate(N,lists:seq(1,255))), +	  OKBin = unicode:characters_to_binary(OKList,unicode,{utf16,big}), +	  OKLen = length(OKList), +	  %% Copy to avoid that the binary get's writable +	  PartlyBroken = binary:copy(<<OKBin/binary, BrokenPart/binary>>), +	  PBSz = byte_size(PartlyBroken), +	  {error,OKList,DeepBrokenPart} =  +	      unicode:characters_to_list(PartlyBroken,{utf16,big}), +	  BrokenPart = iolist_to_binary(DeepBrokenPart), +	  [ begin +		NewList = lists:nthtail(X, OKList), +		NewSz = byte_size(unicode:characters_to_binary(NewList,unicode,{utf16,big})) +  +		    BrokenSz, +		Chomped = binary:part(PartlyBroken,PBSz - NewSz, NewSz), +		true = (binary:referenced_byte_size(Chomped) =:= PBSz), +		{error,NewList,DeepBrokenPart2} =   +		    unicode:characters_to_list(Chomped,{utf16,big}), +		BrokenPart = iolist_to_binary(DeepBrokenPart2) +	    end || X <- lists:seq(1,OKLen) ] +      end || N <- lists:seq(1,15) ], +    ok. +     +ex_binaries_errors_utf32_big(Config) when is_list(Config) -> +    BrokenPart = << <<X:32/big>> || X <- lists:seq(16#DC00,16#DFFF) >>, +    BrokenSz = byte_size(BrokenPart), +    [ begin +	  OKList = lists:flatten(lists:duplicate(N,lists:seq(1,255))), +	  OKBin = unicode:characters_to_binary(OKList,unicode,{utf32,big}), +	  OKLen = length(OKList), +	  %% Copy to avoid that the binary get's writable +	  PartlyBroken = binary:copy(<<OKBin/binary, BrokenPart/binary>>), +	  PBSz = byte_size(PartlyBroken), +	  {error,OKList,DeepBrokenPart} =  +	      unicode:characters_to_list(PartlyBroken,{utf32,big}), +	  BrokenPart = iolist_to_binary(DeepBrokenPart), +	  [ begin +		NewList = lists:nthtail(X, OKList), +		NewSz = byte_size(unicode:characters_to_binary(NewList,unicode,{utf32,big})) +  +		    BrokenSz, +		Chomped = binary:part(PartlyBroken,PBSz - NewSz, NewSz), +		true = (binary:referenced_byte_size(Chomped) =:= PBSz), +		{error,NewList,DeepBrokenPart2} =   +		    unicode:characters_to_list(Chomped,{utf32,big}), +		BrokenPart = iolist_to_binary(DeepBrokenPart2) +	    end || X <- lists:seq(1,OKLen) ] +      end || N <- lists:seq(1,15) ], +    ok. +     +ex_binaries_errors_utf32_little(Config) when is_list(Config) -> +    BrokenPart = << <<X:32/little>> || X <- lists:seq(16#DC00,16#DFFF) >>, +    BrokenSz = byte_size(BrokenPart), +    [ begin +	  OKList = lists:flatten(lists:duplicate(N,lists:seq(1,255))), +	  OKBin = unicode:characters_to_binary(OKList,unicode,{utf32,little}), +	  OKLen = length(OKList), +	  %% Copy to avoid that the binary get's writable +	  PartlyBroken = binary:copy(<<OKBin/binary, BrokenPart/binary>>), +	  PBSz = byte_size(PartlyBroken), +	  {error,OKList,DeepBrokenPart} =  +	      unicode:characters_to_list(PartlyBroken,{utf32,little}), +	  BrokenPart = iolist_to_binary(DeepBrokenPart), +	  [ begin +		NewList = lists:nthtail(X, OKList), +		NewSz = byte_size(unicode:characters_to_binary(NewList,unicode,{utf32,little})) +  +		    BrokenSz, +		Chomped = binary:part(PartlyBroken,PBSz - NewSz, NewSz), +		true = (binary:referenced_byte_size(Chomped) =:= PBSz), +		{error,NewList,DeepBrokenPart2} =   +		    unicode:characters_to_list(Chomped,{utf32,little}), +		BrokenPart = iolist_to_binary(DeepBrokenPart2) +	    end || X <- lists:seq(1,OKLen) ] +      end || N <- lists:seq(1,15) ], +    ok. +      exceptions(Config) when is_list(Config) -> diff --git a/lib/test_server/doc/src/notes.xml b/lib/test_server/doc/src/notes.xml index b35929f1e6..0eab939a46 100644 --- a/lib/test_server/doc/src/notes.xml +++ b/lib/test_server/doc/src/notes.xml @@ -226,8 +226,6 @@  	    unicode:characters_to_binary for conversion between  	    binaries and strings instead of binary_to_list and  	    list_to_binary. </item> </list></p> -          <p> -	    Own Id: OTP-10783</p>          </item>        </list>      </section> diff --git a/lib/test_server/src/configure.in b/lib/test_server/src/configure.in index 785bab395c..b8fddf1481 100644 --- a/lib/test_server/src/configure.in +++ b/lib/test_server/src/configure.in @@ -276,6 +276,7 @@ AC_CHECK_FUNC(gethostbyname, , AC_CHECK_LIB(nsl, main, [LIBS="$LIBS -lnsl"]))  dnl Checks for library functions.  AC_CHECK_FUNCS(strerror)  AC_CHECK_FUNCS(vsnprintf) +AC_CHECK_FUNCS(usleep)  # First check if the library is available, then if we can choose between  # two versions of gethostbyname diff --git a/lib/tools/doc/src/eprof.xml b/lib/tools/doc/src/eprof.xml index 82eb8dd284..8397204a43 100644 --- a/lib/tools/doc/src/eprof.xml +++ b/lib/tools/doc/src/eprof.xml @@ -52,12 +52,14 @@      <func>        <name>start_profiling(Rootset) -> profiling | {error, Reason}</name>        <name>start_profiling(Rootset,Pattern) -> profiling | {error, Reason}</name> +      <name>start_profiling(Rootset,Pattern,Options) -> profiling | {error, Reason}</name>        <fsummary>Start profiling.</fsummary>        <type>          <v>Rootset = [atom() | pid()]</v> -	<v>Pattern = {Module, Function, Arity}</v> -	<v>Module = Function = atom()</v> -	<v>Arity = integer()</v> +		<v>Pattern = {Module, Function, Arity}</v> +		<v>Module = Function = atom()</v> +		<v>Arity = integer()</v> +		<v>Options = [set_on_spawn]</v>          <v>Reason = term()</v>        </type>        <desc> @@ -70,6 +72,9 @@          <p>A pattern can be selected to narrow the profiling. For instance a            specific module can be selected, and only the code executed in that            module will be profiled.</p> +	    <p>The <c>set_on_spawn</c> option will active call time tracing for +			all processes spawned by processes in the rootset. This is +			the default behaviour.</p>        </desc>      </func>      <func> @@ -82,19 +87,22 @@      </func>      <func>        <name>profile(Fun) -> profiling | {error, Reason}</name> +      <name>profile(Fun, Options) -> profiling | {error, Reason}</name>        <name>profile(Rootset) -> profiling | {error, Reason}</name>        <name>profile(Rootset,Fun) -> {ok, Value} | {error,Reason}</name>        <name>profile(Rootset,Fun,Pattern) -> {ok, Value} | {error, Reason}</name>        <name>profile(Rootset,Module,Function,Args) -> {ok, Value} | {error, Reason}</name>        <name>profile(Rootset,Module,Function,Args,Pattern) -> {ok, Value} | {error, Reason}</name> +      <name>profile(Rootset,Module,Function,Args,Pattern,Options) -> {ok, Value} | {error, Reason}</name>        <fsummary>Start profiling.</fsummary>        <type>          <v>Rootset = [atom() | pid()]</v> -        <v>Fun = fun() -> term()</v> -	<v>Pattern = {Module, Function, Arity}</v> +        <v>Fun = fun() -> term() end</v> +		<v>Pattern = {Module, Function, Arity}</v>          <v>Module = Function = atom()</v>          <v>Args = [term()]</v> -	<v>Arity = integer()</v> +		<v>Arity = integer()</v> +		<v>Options = [set_on_spawn]</v>          <v>Value = Reason = term()</v>        </type>        <desc> @@ -108,8 +116,11 @@            <c>Rootset</c>, the function returns <c>{ok,Value}</c> when            <c>Fun()</c>/<c>apply</c> returns with the value <c>Value</c>, or            <c>{error,Reason}</c> if <c>Fun()</c>/<c>apply</c> fails with -	  exit reason <c>Reason</c>. Otherwise it returns <c>{error, Reason}</c> +		  exit reason <c>Reason</c>. Otherwise it returns <c>{error, Reason}</c>            immediately.</p> +	    <p>The <c>set_on_spawn</c> option will active call time tracing for +			all processes spawned by processes in the rootset. This is +			the default behaviour.</p>          <p>The programmer must ensure that the function given as argument            is truly synchronous and that no work continues after            the function has returned a value.</p> diff --git a/lib/tools/src/eprof.erl b/lib/tools/src/eprof.erl index 87fdc1fa34..bc9345fa62 100644 --- a/lib/tools/src/eprof.erl +++ b/lib/tools/src/eprof.erl @@ -26,7 +26,7 @@  -export([start/0,  	 stop/0,  	 dump/0, -	 start_profiling/1, start_profiling/2, +	 start_profiling/1, start_profiling/2, start_profiling/3,  	 profile/1, profile/2, profile/3, profile/4, profile/5,  	 stop_profiling/0,  	 analyze/0, analyze/1, analyze/2, @@ -39,6 +39,8 @@  	 handle_info/2,  	 terminate/2,  	 code_change/3]). + +  -record(bpd, {  	n   = 0,                 % number of total calls  	us  = 0,                 % sum of uS for all calls @@ -46,14 +48,18 @@  	mfa = []                 % list of {Mfa, {Count, Us}}      }). +-define(default_options, [{set_on_spawn, true}]). +-define(default_pattern, {'_','_','_'}). +  -record(state, { -	profiling = false, -	pattern   = {'_','_','_'}, -	rootset   = [], -	fd    = undefined, -	start_ts  = undefined, -	reply     = undefined, -	bpd       = #bpd{} +	profiling  = false, +	pattern    = ?default_pattern, +	rootset    = [], +	trace_opts = [], +	fd         = undefined, +	start_ts   = undefined, +	reply      = undefined, +	bpd        = #bpd{}      }). @@ -67,26 +73,6 @@  start() -> gen_server:start({local, ?MODULE}, ?MODULE, [], []).  stop()  -> gen_server:call(?MODULE, stop, infinity). -profile(Fun) when is_function(Fun) -> -    profile([], Fun); -profile(Rs) when is_list(Rs) -> -    start_profiling(Rs). - -profile(Pids, Fun) -> -    profile(Pids, Fun, {'_','_','_'}). - -profile(Pids, Fun, Pattern) -> -    profile(Pids, erlang, apply, [Fun,[]], Pattern). - -profile(Pids, M, F, A) -> -    profile(Pids, M, F, A, {'_','_','_'}). - -profile(Pids, M, F, A, Pattern) -> -    start(), -    gen_server:call(?MODULE, {profile,Pids,Pattern,M,F,A},infinity). - -dump() ->  -    gen_server:call(?MODULE, dump, infinity).  analyze() ->      analyze(procs). @@ -98,17 +84,53 @@ analyze(Opts) when is_list(Opts) ->  analyze(Type, Opts) when is_list(Opts) ->      gen_server:call(?MODULE, {analyze, Type, Opts}, infinity). +%% odd duck, should only been start_profiling/1 +profile(Rootset) when is_list(Rootset) -> +    start_profiling(Rootset); + +profile(Fun) when is_function(Fun) -> +    profile([], Fun). + +profile(Fun, Opts) when is_function(Fun), is_list(Opts) -> +    profile([], erlang, apply, [Fun, []], ?default_pattern, Opts); + +profile(Rootset, Fun) when is_list(Rootset), is_function(Fun) -> +    profile(Rootset, Fun, ?default_pattern). + +profile(Rootset, Fun, Pattern) when is_list(Rootset), is_function(Fun) -> +    profile(Rootset, Fun, Pattern, ?default_options). + +profile(Rootset, Fun, Pattern, Options) when is_list(Rootset), is_function(Fun), is_list(Options) -> +    profile(Rootset, erlang, apply, [Fun,[]], Pattern, Options); + +profile(Rootset, M, F, A) when is_list(Rootset), is_atom(M), is_atom(F), is_list(A) -> +    profile(Rootset, M, F, A, ?default_pattern). + +profile(Rootset, M, F, A, Pattern) when is_list(Rootset), is_atom(M), is_atom(F), is_list(A) -> +    profile(Rootset, M, F, A, Pattern, ?default_options). + +%% Returns when M:F/A has terminated +profile(Rootset, M, F, A, Pattern, Options) -> +    start(), +    gen_server:call(?MODULE, {profile_start, Rootset, Pattern, {M,F,A}, Options}, infinity). + +dump() ->  +    gen_server:call(?MODULE, dump, infinity). +  log(File) ->      gen_server:call(?MODULE, {logfile, File}, infinity). +%% Does not block  start_profiling(Rootset) -> -    start_profiling(Rootset, {'_','_','_'}). +    start_profiling(Rootset, ?default_pattern).  start_profiling(Rootset, Pattern) -> +    start_profiling(Rootset, Pattern, ?default_options). +start_profiling(Rootset, Pattern, Options) ->      start(), -    gen_server:call(?MODULE, {profile, Rootset, Pattern}, infinity). +    gen_server:call(?MODULE, {profile_start, Rootset, Pattern, undefined, Options}, infinity).  stop_profiling() -> -    gen_server:call(?MODULE, stop_profiling, infinity). +    gen_server:call(?MODULE, profile_stop, infinity).  %% -------------------------------------------------------------------- %% @@ -151,74 +173,75 @@ handle_call({analyze, Type, _Opts}, _, S) ->  %% profile -handle_call({profile, _Rootset, _Pattern, _M,_F,_A}, _From, #state{ profiling = true } = S) -> +handle_call({profile_start, _Rootset, _Pattern, _MFA, _Opts}, _From, #state{ profiling = true } = S) ->      {reply, {error, already_profiling}, S}; -handle_call({profile, Rootset, Pattern, M,F,A}, From, #state{fd = Fd } = S) -> +handle_call({profile_start, Rootset, Pattern, {M,F,A}, Opts}, From, #state{fd = Fd } = S) -> + +    ok = set_pattern_trace(false, S#state.pattern), +    _  = set_process_trace(false, S#state.rootset, S#state.trace_opts), -    set_pattern_trace(false, S#state.pattern), -    set_process_trace(false, S#state.rootset), +    Topts = get_trace_options(Opts), +    Pid   = setup_profiling(M,F,A), -    Pid = setup_profiling(M,F,A), -    case set_process_trace(true, [Pid|Rootset]) of +    case set_process_trace(true, [Pid|Rootset], Topts) of  	true -> -	    set_pattern_trace(true, Pattern), +	    ok = set_pattern_trace(true, Pattern),  	    T0 = now(), -	    execute_profiling(Pid), +	    ok = execute_profiling(Pid),  	    {noreply, #state{ -		    profiling = true, -		    rootset   = [Pid|Rootset], -		    start_ts  = T0, -		    reply     = From, -		    fd        = Fd, -		    pattern   = Pattern +		    profiling  = true, +		    rootset    = [Pid|Rootset], +		    start_ts   = T0, +		    reply      = From, +		    fd         = Fd, +		    trace_opts = Topts, +		    pattern    = Pattern  		}};  	false ->  	    exit(Pid, eprof_kill),  	    {reply, error, #state{ fd = Fd}}      end; -handle_call({profile, _Rootset, _Pattern}, _From, #state{ profiling = true } = S) -> -    {reply, {error, already_profiling}, S}; - -handle_call({profile, Rootset, Pattern}, From, #state{ fd = Fd } = S) -> +handle_call({profile_start, Rootset, Pattern, undefined, Opts}, From, #state{ fd = Fd } = S) -> -    set_pattern_trace(false, S#state.pattern), -    set_process_trace(false, S#state.rootset), +    ok    = set_pattern_trace(false, S#state.pattern), +    true  = set_process_trace(false, S#state.rootset, S#state.trace_opts), +    Topts = get_trace_options(Opts), -    case set_process_trace(true, Rootset) of +    case set_process_trace(true, Rootset, Topts) of  	true ->  	    T0 = now(), -	    set_pattern_trace(true, Pattern), +	    ok = set_pattern_trace(true, Pattern),  	    {reply, profiling, #state{ -		    profiling = true, -		    rootset   = Rootset, -		    start_ts  = T0, -		    reply     = From, -		    fd        = Fd, -		    pattern   = Pattern +		    profiling  = true, +		    rootset    = Rootset, +		    start_ts   = T0, +		    reply      = From, +		    fd         = Fd, +		    trace_opts = Topts, +		    pattern    = Pattern  		}};  	false ->  	    {reply, error, #state{ fd = Fd }}      end; -handle_call(stop_profiling, _From, #state{ profiling = false } = S) -> +handle_call(profile_stop, _From, #state{ profiling = false } = S) ->      {reply, profiling_already_stopped, S}; -handle_call(stop_profiling, _From, #state{ profiling = true } = S) -> - -    set_pattern_trace(pause, S#state.pattern), +handle_call(profile_stop, _From, #state{ profiling = true } = S) -> +    ok  = set_pattern_trace(pause, S#state.pattern),      Bpd = collect_bpd(), - -    set_process_trace(false, S#state.rootset), -    set_pattern_trace(false, S#state.pattern), +    _   = set_process_trace(false, S#state.rootset, S#state.trace_opts), +    ok  = set_pattern_trace(false, S#state.pattern),      {reply, profiling_stopped, S#state{ -	profiling = false, -	rootset   = [], -	pattern   = {'_','_','_'}, -	bpd       = Bpd +	profiling  = false, +	rootset    = [], +	trace_opts = [], +	pattern    = ?default_pattern, +	bpd        = Bpd      }};  %% logfile @@ -261,33 +284,33 @@ handle_info({'EXIT', _, eprof_kill}, S) ->      {noreply, S};  handle_info({'EXIT', _, Reason}, #state{ reply = FromTag } = S) -> -    set_process_trace(false, S#state.rootset), -    set_pattern_trace(false, S#state.pattern), +    _  = set_process_trace(false, S#state.rootset, S#state.trace_opts), +    ok = set_pattern_trace(false, S#state.pattern),      gen_server:reply(FromTag, {error, Reason}),      {noreply, S#state{ -	profiling = false, -	rootset   = [], -	pattern   = {'_','_','_'} +	profiling  = false, +	rootset    = [], +	trace_opts = [], +	pattern    = ?default_pattern      }};  % check if Pid is spawned process?  handle_info({_Pid, {answer, Result}}, #state{ reply = {From,_} = FromTag} = S) -> -    set_pattern_trace(pause, S#state.pattern), - -    Bpd = collect_bpd(), - -    set_process_trace(false, S#state.rootset), -    set_pattern_trace(false, S#state.pattern), +    ok   = set_pattern_trace(pause, S#state.pattern), +    Bpd  = collect_bpd(), +    _    = set_process_trace(false, S#state.rootset, S#state.trace_opts), +    ok   = set_pattern_trace(false, S#state.pattern),      catch unlink(From),      gen_server:reply(FromTag, {ok, Result}),      {noreply, S#state{ -	profiling = false, -	rootset   = [], -	pattern   = {'_','_','_'}, -	bpd       = Bpd +	profiling  = false, +	rootset    = [], +	trace_opts = [], +	pattern    = ?default_pattern, +	bpd        = Bpd      }}.  %% -------------------------------------------------------------------- %% @@ -297,11 +320,11 @@ handle_info({_Pid, {answer, Result}}, #state{ reply = {From,_} = FromTag} = S) -  %% -------------------------------------------------------------------- %%  terminate(_Reason, #state{ fd = undefined }) -> -    set_pattern_trace(false, {'_','_','_'}), +    ok = set_pattern_trace(false, ?default_pattern),      ok;  terminate(_Reason, #state{ fd = Fd }) -> -    file:close(Fd), -    set_pattern_trace(false, {'_','_','_'}), +    ok = file:close(Fd), +    ok = set_pattern_trace(false, ?default_pattern),      ok.  %% -------------------------------------------------------------------- %% @@ -330,7 +353,19 @@ spin_profile(M, F, A) ->      end.  execute_profiling(Pid) -> -    Pid ! {self(), execute}. +    Pid ! {self(), execute}, +    ok. + + +get_trace_options([]) -> +    [call]; +get_trace_options([{set_on_spawn, true}|Opts]) -> +    [set_on_spawn | get_trace_options(Opts)]; +get_trace_options([set_on_spawn|Opts]) -> +    [set_on_spawn | get_trace_options(Opts)]; +get_trace_options([_|Opts]) -> +    get_trace_options(Opts). +  set_pattern_trace(Flag, Pattern) ->      erlang:system_flag(multi_scheduling, block), @@ -339,10 +374,6 @@ set_pattern_trace(Flag, Pattern) ->      erlang:system_flag(multi_scheduling, unblock),      ok. -set_process_trace(Flag, Pids) -> -    % do we need procs for meta info? -    % could be useful -    set_process_trace(Flag, Pids, [call, set_on_spawn]).  set_process_trace(_, [], _) -> true;  set_process_trace(Flag, [Pid|Pids], Options) when is_pid(Pid) ->      try diff --git a/lib/tools/test/eprof_SUITE.erl b/lib/tools/test/eprof_SUITE.erl index 3283fa571f..148622cf07 100644 --- a/lib/tools/test/eprof_SUITE.erl +++ b/lib/tools/test/eprof_SUITE.erl @@ -21,12 +21,14 @@  -include_lib("test_server/include/test_server.hrl").  -export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1,  -	 init_per_group/2,end_per_group/2,tiny/1,eed/1,basic/1]). +	init_per_group/2,end_per_group/2]). + +-export([tiny/1,eed/1,basic/1,basic_option/1]).  suite() -> [{ct_hooks,[ts_install_cth]}].  all() ->  -    [basic, tiny, eed]. +    [basic, basic_option, tiny, eed].  groups() ->       []. @@ -49,140 +51,185 @@ basic(Config) when is_list(Config) ->      %% load eprof_test and change directory -    ?line {ok, OldCurDir} = file:get_cwd(), +    {ok, OldCurDir} = file:get_cwd(),      Datadir = ?config(data_dir, Config),      Privdir = ?config(priv_dir, Config), -    ?line {ok,eprof_test} = compile:file(filename:join(Datadir, "eprof_test"), +    {ok,eprof_test} = compile:file(filename:join(Datadir, "eprof_test"),  					       [trace,{outdir, Privdir}]), -    ?line ok = file:set_cwd(Privdir), -    ?line code:purge(eprof_test), -    ?line {module,eprof_test} = code:load_file(eprof_test), +    ok = file:set_cwd(Privdir), +    code:purge(eprof_test), +    {module,eprof_test} = code:load_file(eprof_test),      %% rootset profiling -    ?line ensure_eprof_stopped(), -    ?line profiling = eprof:profile([self()]), -    ?line {error, already_profiling} = eprof:profile([self()]), -    ?line profiling_stopped = eprof:stop_profiling(), -    ?line profiling_already_stopped = eprof:stop_profiling(), -    ?line profiling = eprof:start_profiling([self(),self(),self()]), -    ?line profiling_stopped = eprof:stop_profiling(), +    ensure_eprof_stopped(), +    profiling = eprof:profile([self()]), +    {error, already_profiling} = eprof:profile([self()]), +    profiling_stopped = eprof:stop_profiling(), +    profiling_already_stopped = eprof:stop_profiling(), +    profiling = eprof:start_profiling([self(),self(),self()]), +    profiling_stopped = eprof:stop_profiling(),      %% with patterns -    ?line profiling = eprof:start_profiling([self()], {?MODULE, '_', '_'}), -    ?line {error, already_profiling} = eprof:start_profiling([self()], {?MODULE, '_', '_'}), -    ?line profiling_stopped = eprof:stop_profiling(), -    ?line profiling = eprof:start_profiling([self()], {?MODULE, start_stop, '_'}), -    ?line profiling_stopped = eprof:stop_profiling(), -    ?line profiling = eprof:start_profiling([self()], {?MODULE, start_stop, 1}), -    ?line profiling_stopped = eprof:stop_profiling(), +    profiling = eprof:start_profiling([self()], {?MODULE, '_', '_'}), +    {error, already_profiling} = eprof:start_profiling([self()], {?MODULE, '_', '_'}), +    profiling_stopped = eprof:stop_profiling(), +    profiling = eprof:start_profiling([self()], {?MODULE, start_stop, '_'}), +    profiling_stopped = eprof:stop_profiling(), +    profiling = eprof:start_profiling([self()], {?MODULE, start_stop, 1}), +    profiling_stopped = eprof:stop_profiling(),      %% with fun -    ?line {ok, _} = eprof:profile(fun() -> eprof_test:go(10) end), -    ?line profiling = eprof:profile([self()]), -    ?line {error, already_profiling} = eprof:profile(fun() -> eprof_test:go(10) end), -    ?line profiling_stopped = eprof:stop_profiling(), -    ?line {ok, _} = eprof:profile(fun() -> eprof_test:go(10) end), -    ?line {ok, _} = eprof:profile([], fun() -> eprof_test:go(10) end), -    ?line Pid     = whereis(eprof), -    ?line {ok, _} = eprof:profile(erlang:processes() -- [Pid], fun() -> eprof_test:go(10) end), -    ?line {ok, _} = eprof:profile([], fun() -> eprof_test:go(10) end, {eprof_test, '_', '_'}), -    ?line {ok, _} = eprof:profile([], fun() -> eprof_test:go(10) end, {eprof_test, go, '_'}), -    ?line {ok, _} = eprof:profile([], fun() -> eprof_test:go(10) end, {eprof_test, go, 1}), -    ?line {ok, _} = eprof:profile([], fun() -> eprof_test:go(10) end, {eprof_test, dec, 1}), +    {ok, _} = eprof:profile(fun() -> eprof_test:go(10) end), +    profiling = eprof:profile([self()]), +    {error, already_profiling} = eprof:profile(fun() -> eprof_test:go(10) end), +    profiling_stopped = eprof:stop_profiling(), +    {ok, _} = eprof:profile(fun() -> eprof_test:go(10) end), +    {ok, _} = eprof:profile([], fun() -> eprof_test:go(10) end), +    Pid     = whereis(eprof), +    {ok, _} = eprof:profile(erlang:processes() -- [Pid], fun() -> eprof_test:go(10) end), +    {ok, _} = eprof:profile([], fun() -> eprof_test:go(10) end, {eprof_test, '_', '_'}), +    {ok, _} = eprof:profile([], fun() -> eprof_test:go(10) end, {eprof_test, go, '_'}), +    {ok, _} = eprof:profile([], fun() -> eprof_test:go(10) end, {eprof_test, go, 1}), +    {ok, _} = eprof:profile([], fun() -> eprof_test:go(10) end, {eprof_test, dec, 1}),      %% error case -    ?line error     = eprof:profile([Pid], fun() -> eprof_test:go(10) end), -    ?line Pid       = whereis(eprof), -    ?line error     = eprof:profile([Pid], fun() -> eprof_test:go(10) end), -    ?line A         = spawn(fun() -> receive _ -> ok end end), -    ?line profiling = eprof:profile([A]), -    ?line true      = exit(A, kill_it), -    ?line profiling_stopped = eprof:stop_profiling(), -    ?line {error,_} = eprof:profile(fun() -> a = b end), +    error     = eprof:profile([Pid], fun() -> eprof_test:go(10) end), +    Pid       = whereis(eprof), +    error     = eprof:profile([Pid], fun() -> eprof_test:go(10) end), +    A         = spawn(fun() -> receive _ -> ok end end), +    profiling = eprof:profile([A]), +    true      = exit(A, kill_it), +    profiling_stopped = eprof:stop_profiling(), +    {error,_} = eprof:profile(fun() -> a = b end),      %% with mfa -    ?line {ok, _} = eprof:profile([], eprof_test, go, [10]), -    ?line {ok, _} = eprof:profile([], eprof_test, go, [10], {eprof_test, dec, 1}), +    {ok, _} = eprof:profile([], eprof_test, go, [10]), +    {ok, _} = eprof:profile([], eprof_test, go, [10], {eprof_test, dec, 1}),      %% dump -    ?line {ok, _} = eprof:profile([], fun() -> eprof_test:go(10) end, {eprof_test, '_', '_'}), -    ?line [{_, Mfas}] = eprof:dump(), -    ?line Dec_mfa = {eprof_test, dec, 1}, -    ?line Go_mfa  = {eprof_test, go,  1}, -    ?line {value, {Go_mfa,  { 1, _Time1}}} = lists:keysearch(Go_mfa,  1, Mfas), -    ?line {value, {Dec_mfa, {11, _Time2}}} = lists:keysearch(Dec_mfa, 1, Mfas), +    {ok, _} = eprof:profile([], fun() -> eprof_test:go(10) end, {eprof_test, '_', '_'}), +    [{_, Mfas}] = eprof:dump(), +    Dec_mfa = {eprof_test, dec, 1}, +    Go_mfa  = {eprof_test, go,  1}, +    {value, {Go_mfa,  { 1, _Time1}}} = lists:keysearch(Go_mfa,  1, Mfas), +    {value, {Dec_mfa, {11, _Time2}}} = lists:keysearch(Dec_mfa, 1, Mfas),      %% change current working directory -    ?line ok = file:set_cwd(OldCurDir), -    ?line stopped = eprof:stop(), +    ok = file:set_cwd(OldCurDir), +    stopped = eprof:stop(), +    ok. + +basic_option(Config) when is_list(Config) -> +    %% load eprof_test and change directory + +    {ok, OldCurDir} = file:get_cwd(), +    Datadir = ?config(data_dir, Config), +    Privdir = ?config(priv_dir, Config), +    {ok,eprof_test} = compile:file(filename:join(Datadir, "eprof_test"), +					       [trace,{outdir, Privdir}]), +    ok = file:set_cwd(Privdir), +    code:purge(eprof_test), +    {module,eprof_test} = code:load_file(eprof_test), + +    % vanilla +    {ok, _} = eprof:profile(fun() -> eprof_test:do(10) end, [{set_on_spawn, true}]), + +    [{_, MfasDo1},{_, MfasLists1}] = eprof:dump(), +    Mfas1 = MfasDo1 ++ MfasLists1, + +    {value, {_, {11, _}}} = lists:keysearch({eprof_test,dec,1},  1, Mfas1), +    {value, {_, { 1, _}}} = lists:keysearch({eprof_test, go,1},  1, Mfas1), +    {value, {_, { 9, _}}} = lists:keysearch({lists, split_2,5},  1, Mfas1), +    {value, {_, { 4, _}}} = lists:keysearch({lists, seq_loop,3}, 1, Mfas1), + +    {ok, _} = eprof:profile(fun() -> eprof_test:do(10) end, [set_on_spawn]), + +    [{_, MfasDo2},{_, MfasLists2}] = eprof:dump(), +    Mfas2 = MfasDo2 ++ MfasLists2, +    {value, {_, {11, _}}} = lists:keysearch({eprof_test,dec,1},  1, Mfas2), +    {value, {_, { 1, _}}} = lists:keysearch({eprof_test, go,1},  1, Mfas2), +    {value, {_, { 9, _}}} = lists:keysearch({lists, split_2,5},  1, Mfas2), +    {value, {_, { 4, _}}} = lists:keysearch({lists, seq_loop,3}, 1, Mfas2), + +    % disable trace set_on_spawn +    {ok, _} = eprof:profile(fun() -> eprof_test:do(10) end, []), +    [{_, Mfas3}] = eprof:dump(), +    {value, {_, {11, _}}} = lists:keysearch({eprof_test,dec,1}, 1, Mfas3), +    {value, {_, { 1, _}}} = lists:keysearch({eprof_test, go,1}, 1, Mfas3), +    false = lists:keysearch({lists, split_2,5},  1, Mfas3), +    false = lists:keysearch({lists, seq_loop,3}, 1, Mfas3), + +    %% change current working directory +    ok = file:set_cwd(OldCurDir), +    stopped = eprof:stop(),      ok.  tiny(suite) -> [];  tiny(Config) when is_list(Config) ->  -    ?line ensure_eprof_stopped(), -    ?line {ok, OldCurDir} = file:get_cwd(), +    ensure_eprof_stopped(), +    {ok, OldCurDir} = file:get_cwd(),      Datadir = ?config(data_dir, Config),      Privdir = ?config(priv_dir, Config), -    ?line TTrap=?t:timetrap(60*1000), +    TTrap=?t:timetrap(60*1000),      % (Trace)Compile to priv_dir and make sure the correct version is loaded. -    ?line {ok,eprof_suite_test} = compile:file(filename:join(Datadir, +    {ok,eprof_suite_test} = compile:file(filename:join(Datadir,  							     "eprof_suite_test"),  					       [trace,{outdir, Privdir}]), -    ?line ok = file:set_cwd(Privdir), -    ?line code:purge(eprof_suite_test), -    ?line {module,eprof_suite_test} = code:load_file(eprof_suite_test), -    ?line {ok,_Pid} = eprof:start(), -    ?line nothing_to_analyze = eprof:analyze(), -    ?line nothing_to_analyze = eprof:analyze(total), -    ?line eprof:profile([], eprof_suite_test, test, [Config]), -    ?line ok = eprof:analyze(), -    ?line ok = eprof:analyze(total), -    ?line ok = eprof:log("eprof_SUITE_logfile"), -    ?line stopped = eprof:stop(), -    ?line ?t:timetrap_cancel(TTrap), -    ?line ok = file:set_cwd(OldCurDir), +    ok = file:set_cwd(Privdir), +    code:purge(eprof_suite_test), +    {module,eprof_suite_test} = code:load_file(eprof_suite_test), +    {ok,_Pid} = eprof:start(), +    nothing_to_analyze = eprof:analyze(), +    nothing_to_analyze = eprof:analyze(total), +    eprof:profile([], eprof_suite_test, test, [Config]), +    ok = eprof:analyze(), +    ok = eprof:analyze(total), +    ok = eprof:log("eprof_SUITE_logfile"), +    stopped = eprof:stop(), +    ?t:timetrap_cancel(TTrap), +    ok = file:set_cwd(OldCurDir),      ok.  eed(suite) -> [];  eed(Config) when is_list(Config) -> -    ?line ensure_eprof_stopped(), -    ?line Datadir = ?config(data_dir, Config), -    ?line Privdir = ?config(priv_dir, Config), -    ?line TTrap=?t:timetrap(5*60*1000), +    ensure_eprof_stopped(), +    Datadir = ?config(data_dir, Config), +    Privdir = ?config(priv_dir, Config), +    TTrap=?t:timetrap(5*60*1000),      %% (Trace)Compile to priv_dir and make sure the correct version is loaded. -    ?line code:purge(eed), -    ?line {ok,eed} = c:c(filename:join(Datadir, "eed"), [trace,{outdir,Privdir}]), -    ?line {ok,_Pid} = eprof:start(), -    ?line Script = filename:join(Datadir, "ed.script"), -    ?line ok = file:set_cwd(Datadir), -    ?line {T1,_} = statistics(runtime), -    ?line ok = eed:file(Script), -    ?line ok = eed:file(Script), -    ?line ok = eed:file(Script), -    ?line ok = eed:file(Script), -    ?line ok = eed:file(Script), -    ?line ok = eed:file(Script), -    ?line ok = eed:file(Script), -    ?line ok = eed:file(Script), -    ?line ok = eed:file(Script), -    ?line ok = eed:file(Script), -    ?line {T2,_} = statistics(runtime), -    ?line {ok,ok} = eprof:profile([], eed, file, [Script]), -    ?line {T3,_} = statistics(runtime), -    ?line profiling_already_stopped = eprof:stop_profiling(), -    ?line ok = eprof:analyze(), -    ?line ok = eprof:analyze(total), -    ?line ok = eprof:log("eprof_SUITE_logfile"), -    ?line stopped = eprof:stop(), -    ?line ?t:timetrap_cancel(TTrap), +    code:purge(eed), +    {ok,eed} = c:c(filename:join(Datadir, "eed"), [trace,{outdir,Privdir}]), +    {ok,_Pid} = eprof:start(), +    Script = filename:join(Datadir, "ed.script"), +    ok = file:set_cwd(Datadir), +    {T1,_} = statistics(runtime), +    ok = eed:file(Script), +    ok = eed:file(Script), +    ok = eed:file(Script), +    ok = eed:file(Script), +    ok = eed:file(Script), +    ok = eed:file(Script), +    ok = eed:file(Script), +    ok = eed:file(Script), +    ok = eed:file(Script), +    ok = eed:file(Script), +    {T2,_} = statistics(runtime), +    {ok,ok} = eprof:profile([], eed, file, [Script]), +    {T3,_} = statistics(runtime), +    profiling_already_stopped = eprof:stop_profiling(), +    ok = eprof:analyze(), +    ok = eprof:analyze(total), +    ok = eprof:log("eprof_SUITE_logfile"), +    stopped = eprof:stop(), +    ?t:timetrap_cancel(TTrap),      try  	S = lists:flatten(io_lib:format("~p times slower",  					[10*(T3-T2)/(T2-T1)])), @@ -198,5 +245,5 @@ ensure_eprof_stopped() ->  	undefined ->  	    ok;  	Pid -> -	    ?line stopped=eprof:stop() +	    stopped=eprof:stop()      end. diff --git a/lib/tools/test/eprof_SUITE_data/eprof_test.erl b/lib/tools/test/eprof_SUITE_data/eprof_test.erl index 33c428e893..2d9e4c2945 100644 --- a/lib/tools/test/eprof_SUITE_data/eprof_test.erl +++ b/lib/tools/test/eprof_SUITE_data/eprof_test.erl @@ -1,5 +1,5 @@  -module(eprof_test). --export([go/1]). +-export([go/1, do/1]).  go(N) ->      0 = dec(N), @@ -7,3 +7,16 @@ go(N) ->  dec(0) -> 0;  dec(N) -> dec(N - 1). + + + +load(N, Pid) -> +    _ = lists:sort(lists:reverse(lists:seq(1, N))), +    Pid ! {self(), ok}. + + +do(N) -> +    Me  = self(), +    Pid = spawn_link(fun() -> load(N, Me) end), +    ok  = go(N), +    receive {Pid, ok} -> ok end.  | 
