aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--aclocal.m4106
-rw-r--r--erts/aclocal.m4106
-rw-r--r--erts/configure.in59
-rw-r--r--erts/emulator/beam/erl_bits.c10
-rw-r--r--erts/emulator/drivers/common/inet_drv.c73
-rw-r--r--erts/emulator/hipe/hipe_bif0.c41
-rw-r--r--erts/emulator/test/float_SUITE.erl9
-rwxr-xr-xerts/emulator/utils/make_preload1
-rw-r--r--erts/etc/common/erlc.c2
-rw-r--r--erts/lib_src/common/erl_printf_format.c13
-rw-r--r--erts/preloaded/ebin/init.beambin48292 -> 48604 bytes
-rw-r--r--erts/preloaded/ebin/prim_inet.beambin70100 -> 69952 bytes
-rw-r--r--erts/preloaded/src/init.erl8
-rw-r--r--erts/preloaded/src/prim_inet.erl6
-rw-r--r--lib/common_test/doc/src/ct_run.xml18
-rw-r--r--lib/common_test/doc/src/run_test_chapter.xml8
-rw-r--r--lib/common_test/include/ct.hrl13
-rw-r--r--lib/common_test/priv/Makefile.in8
-rw-r--r--lib/common_test/priv/ct_default.css17
-rw-r--r--lib/common_test/priv/jquery-latest.js154
-rw-r--r--lib/common_test/priv/jquery.tablesorter.min.js4
-rw-r--r--lib/common_test/src/ct.erl235
-rw-r--r--lib/common_test/src/ct_config.erl6
-rw-r--r--lib/common_test/src/ct_framework.erl44
-rw-r--r--lib/common_test/src/ct_gen_conn.erl15
-rw-r--r--lib/common_test/src/ct_hooks.erl4
-rw-r--r--lib/common_test/src/ct_logs.erl486
-rw-r--r--lib/common_test/src/ct_master_logs.erl106
-rw-r--r--lib/common_test/src/ct_netconfc.erl14
-rw-r--r--lib/common_test/src/ct_repeat.erl88
-rw-r--r--lib/common_test/src/ct_run.erl709
-rw-r--r--lib/common_test/src/ct_ssh.erl283
-rw-r--r--lib/common_test/src/ct_testspec.erl935
-rw-r--r--lib/common_test/src/ct_util.erl63
-rw-r--r--lib/common_test/src/ct_util.hrl10
-rw-r--r--lib/common_test/test/Makefile6
-rw-r--r--lib/common_test/test/ct_auto_compile_SUITE.erl187
-rw-r--r--lib/common_test/test/ct_auto_compile_SUITE_data/bad_SUITE.erl23
-rw-r--r--lib/common_test/test/ct_auto_compile_SUITE_data/dummy_SUITE.erl130
-rw-r--r--lib/common_test/test/ct_basic_html_SUITE.erl180
-rw-r--r--lib/common_test/test/ct_basic_html_SUITE_data/babbling_SUITE.erl130
-rw-r--r--lib/common_test/test/ct_error_SUITE.erl6
-rw-r--r--lib/common_test/test/ct_misc_1_SUITE.erl2
-rw-r--r--lib/common_test/test/ct_priv_dir_SUITE_data/priv_dir_SUITE.erl26
-rw-r--r--lib/common_test/test/ct_test_support.erl50
-rw-r--r--lib/common_test/test/ct_testspec_1_SUITE.erl4
-rw-r--r--lib/common_test/test/ct_testspec_2_SUITE.erl751
-rw-r--r--lib/common_test/test/ct_verbosity_SUITE.erl244
-rw-r--r--lib/common_test/test/ct_verbosity_SUITE_data/io_test_SUITE.erl156
-rw-r--r--lib/common_test/vsn.mk2
-rw-r--r--lib/compiler/test/compile_SUITE.erl6
-rw-r--r--lib/compiler/test/compile_SUITE_data/simple-phony.mk3
-rw-r--r--lib/erl_interface/aclocal.m4106
-rw-r--r--lib/kernel/src/heart.erl4
-rw-r--r--lib/kernel/src/hipe_unified_loader.erl7
-rw-r--r--lib/kernel/src/inet.erl42
-rw-r--r--lib/kernel/test/code_SUITE.erl3
-rw-r--r--lib/kernel/test/gen_sctp_SUITE.erl198
-rw-r--r--lib/mnesia/src/mnesia.appup.src6
-rw-r--r--lib/mnesia/src/mnesia_index.erl6
-rw-r--r--lib/mnesia/test/mnesia_dirty_access_test.erl3
-rw-r--r--lib/mnesia/test/mnesia_trans_access_test.erl4
-rw-r--r--lib/observer/src/observer.app.src12
-rw-r--r--lib/observer/src/observer_tv_table.erl24
-rw-r--r--lib/odbc/aclocal.m4106
-rw-r--r--lib/odbc/c_src/odbcserver.c60
-rw-r--r--lib/odbc/c_src/odbcserver.h2
-rw-r--r--lib/odbc/src/odbc.appup.src8
-rw-r--r--lib/odbc/src/odbc.erl31
-rw-r--r--lib/odbc/vsn.mk2
-rw-r--r--lib/ssh/test/ssh_basic_SUITE.erl8
-rw-r--r--lib/syntax_tools/doc/overview.edoc6
-rw-r--r--lib/syntax_tools/src/epp_dodger.erl4
-rw-r--r--lib/syntax_tools/src/erl_comment_scan.erl2
-rw-r--r--lib/syntax_tools/src/erl_prettypr.erl4
-rw-r--r--lib/syntax_tools/src/erl_recomment.erl4
-rw-r--r--lib/syntax_tools/src/erl_syntax.erl1923
-rw-r--r--lib/syntax_tools/src/erl_syntax_lib.erl4
-rw-r--r--lib/syntax_tools/src/erl_tidy.erl4
-rw-r--r--lib/syntax_tools/src/igor.erl4
-rw-r--r--lib/syntax_tools/src/prettypr.erl4
-rw-r--r--lib/syntax_tools/vsn.mk2
-rw-r--r--lib/test_server/src/test_server.erl290
-rw-r--r--lib/test_server/src/test_server_ctrl.erl85
-rw-r--r--lib/test_server/vsn.mk2
-rw-r--r--lib/wx/aclocal.m4106
-rw-r--r--xcomp/README.md4
-rw-r--r--xcomp/erl-xcomp-TileraMDE2.0-tilepro.conf9
-rw-r--r--xcomp/erl-xcomp-avr32-atmel-linux-gnu.conf9
-rw-r--r--xcomp/erl-xcomp-mips-linux.conf9
-rw-r--r--xcomp/erl-xcomp-vars.sh2
-rw-r--r--xcomp/erl-xcomp-vxworks_ppc32.conf9
-rw-r--r--xcomp/erl-xcomp-x86_64-saf-linux-gnu.conf9
-rw-r--r--xcomp/erl-xcomp.conf.template9
94 files changed, 6163 insertions, 2533 deletions
diff --git a/aclocal.m4 b/aclocal.m4
index 339a15a2bb..a76594d86f 100644
--- a/aclocal.m4
+++ b/aclocal.m4
@@ -59,6 +59,7 @@ AC_ARG_VAR(erl_xcomp_isysroot, [Absolute cross system root include path (only us
dnl Cross compilation variables
AC_ARG_VAR(erl_xcomp_bigendian, [big endian system: yes|no (only used when cross compiling)])
+AC_ARG_VAR(erl_xcomp_double_middle_endian, [double-middle-endian system: yes|no (only used when cross compiling)])
AC_ARG_VAR(erl_xcomp_linux_clock_gettime_correction, [clock_gettime() can be used for time correction: yes|no (only used when cross compiling)])
AC_ARG_VAR(erl_xcomp_linux_nptl, [have Native POSIX Thread Library: yes|no (only used when cross compiling)])
AC_ARG_VAR(erl_xcomp_linux_usable_sigusrx, [SIGUSR1 and SIGUSR2 can be used: yes|no (only used when cross compiling)])
@@ -606,6 +607,103 @@ ifelse([$5], , , [$5
fi
])
+dnl ----------------------------------------------------------------------
+dnl
+dnl AC_DOUBLE_MIDDLE_ENDIAN
+dnl
+dnl Checks whether doubles are represented in "middle-endian" format.
+dnl Sets ac_cv_double_middle_endian={no,yes,unknown} accordingly,
+dnl as well as DOUBLE_MIDDLE_ENDIAN.
+dnl
+dnl
+
+AC_DEFUN([AC_C_DOUBLE_MIDDLE_ENDIAN],
+[AC_CACHE_CHECK(whether double word ordering is middle-endian, ac_cv_c_double_middle_endian,
+[# It does not; compile a test program.
+AC_RUN_IFELSE(
+[AC_LANG_SOURCE([[#include <stdlib.h>
+
+int
+main(void)
+{
+ int i = 0;
+ int zero = 0;
+ int bigendian;
+ int zero_index = 0;
+
+ union
+ {
+ long int l;
+ char c[sizeof (long int)];
+ } u;
+
+ /* we'll use the one with 32-bit words */
+ union
+ {
+ double d;
+ unsigned int c[2];
+ } vint;
+
+ union
+ {
+ double d;
+ unsigned long c[2];
+ } vlong;
+
+ union
+ {
+ double d;
+ unsigned short c[2];
+ } vshort;
+
+
+ /* Are we little or big endian? From Harbison&Steele. */
+ u.l = 1;
+ bigendian = (u.c[sizeof (long int) - 1] == 1);
+
+ zero_index = bigendian ? 1 : 0;
+
+ vint.d = 1.0;
+ vlong.d = 1.0;
+ vshort.d = 1.0;
+
+ if (sizeof(unsigned int) == 4)
+ {
+ if (vint.c[zero_index] != 0)
+ zero = 1;
+ }
+ else if (sizeof(unsigned long) == 4)
+ {
+ if (vlong.c[zero_index] != 0)
+ zero = 1;
+ }
+ else if (sizeof(unsigned short) == 4)
+ {
+ if (vshort.c[zero_index] != 0)
+ zero = 1;
+ }
+
+ exit (zero);
+}
+]])],
+ [ac_cv_c_double_middle_endian=no],
+ [ac_cv_c_double_middle_endian=yes],
+ [ac_cv_c_double_middle=unknown])])
+case $ac_cv_c_double_middle_endian in
+ yes)
+ m4_default([$1],
+ [AC_DEFINE([DOUBLE_MIDDLE_ENDIAN], 1,
+ [Define to 1 if your processor stores the words in a double in
+ middle-endian format (like some ARMs).])]) ;;
+ no)
+ $2 ;;
+ *)
+ m4_default([$3],
+ [AC_MSG_WARN([unknown double endianness
+presetting ac_cv_c_double_middle_endian=no (or yes) will help])]) ;;
+esac
+])# AC_C_DOUBLE_MIDDLE_ENDIAN
+
dnl ----------------------------------------------------------------------
dnl
@@ -1337,6 +1435,14 @@ if test "$ac_cv_c_bigendian" = "yes"; then
AC_DEFINE(ETHR_BIGENDIAN, 1, [Define if bigendian])
fi
+case X$erl_xcomp_double_middle_endian in
+ X) ;;
+ Xyes|Xno|Xunknown) ac_cv_c_double_middle_endian=$erl_xcomp_double_middle_endian;;
+ *) AC_MSG_ERROR([Bad erl_xcomp_double_middle_endian value: $erl_xcomp_double_middle_endian]);;
+esac
+
+AC_C_DOUBLE_MIDDLE_ENDIAN
+
AC_ARG_ENABLE(native-ethr-impls,
AS_HELP_STRING([--disable-native-ethr-impls],
[disable native ethread implementations]),
diff --git a/erts/aclocal.m4 b/erts/aclocal.m4
index 339a15a2bb..a76594d86f 100644
--- a/erts/aclocal.m4
+++ b/erts/aclocal.m4
@@ -59,6 +59,7 @@ AC_ARG_VAR(erl_xcomp_isysroot, [Absolute cross system root include path (only us
dnl Cross compilation variables
AC_ARG_VAR(erl_xcomp_bigendian, [big endian system: yes|no (only used when cross compiling)])
+AC_ARG_VAR(erl_xcomp_double_middle_endian, [double-middle-endian system: yes|no (only used when cross compiling)])
AC_ARG_VAR(erl_xcomp_linux_clock_gettime_correction, [clock_gettime() can be used for time correction: yes|no (only used when cross compiling)])
AC_ARG_VAR(erl_xcomp_linux_nptl, [have Native POSIX Thread Library: yes|no (only used when cross compiling)])
AC_ARG_VAR(erl_xcomp_linux_usable_sigusrx, [SIGUSR1 and SIGUSR2 can be used: yes|no (only used when cross compiling)])
@@ -606,6 +607,103 @@ ifelse([$5], , , [$5
fi
])
+dnl ----------------------------------------------------------------------
+dnl
+dnl AC_DOUBLE_MIDDLE_ENDIAN
+dnl
+dnl Checks whether doubles are represented in "middle-endian" format.
+dnl Sets ac_cv_double_middle_endian={no,yes,unknown} accordingly,
+dnl as well as DOUBLE_MIDDLE_ENDIAN.
+dnl
+dnl
+
+AC_DEFUN([AC_C_DOUBLE_MIDDLE_ENDIAN],
+[AC_CACHE_CHECK(whether double word ordering is middle-endian, ac_cv_c_double_middle_endian,
+[# It does not; compile a test program.
+AC_RUN_IFELSE(
+[AC_LANG_SOURCE([[#include <stdlib.h>
+
+int
+main(void)
+{
+ int i = 0;
+ int zero = 0;
+ int bigendian;
+ int zero_index = 0;
+
+ union
+ {
+ long int l;
+ char c[sizeof (long int)];
+ } u;
+
+ /* we'll use the one with 32-bit words */
+ union
+ {
+ double d;
+ unsigned int c[2];
+ } vint;
+
+ union
+ {
+ double d;
+ unsigned long c[2];
+ } vlong;
+
+ union
+ {
+ double d;
+ unsigned short c[2];
+ } vshort;
+
+
+ /* Are we little or big endian? From Harbison&Steele. */
+ u.l = 1;
+ bigendian = (u.c[sizeof (long int) - 1] == 1);
+
+ zero_index = bigendian ? 1 : 0;
+
+ vint.d = 1.0;
+ vlong.d = 1.0;
+ vshort.d = 1.0;
+
+ if (sizeof(unsigned int) == 4)
+ {
+ if (vint.c[zero_index] != 0)
+ zero = 1;
+ }
+ else if (sizeof(unsigned long) == 4)
+ {
+ if (vlong.c[zero_index] != 0)
+ zero = 1;
+ }
+ else if (sizeof(unsigned short) == 4)
+ {
+ if (vshort.c[zero_index] != 0)
+ zero = 1;
+ }
+
+ exit (zero);
+}
+]])],
+ [ac_cv_c_double_middle_endian=no],
+ [ac_cv_c_double_middle_endian=yes],
+ [ac_cv_c_double_middle=unknown])])
+case $ac_cv_c_double_middle_endian in
+ yes)
+ m4_default([$1],
+ [AC_DEFINE([DOUBLE_MIDDLE_ENDIAN], 1,
+ [Define to 1 if your processor stores the words in a double in
+ middle-endian format (like some ARMs).])]) ;;
+ no)
+ $2 ;;
+ *)
+ m4_default([$3],
+ [AC_MSG_WARN([unknown double endianness
+presetting ac_cv_c_double_middle_endian=no (or yes) will help])]) ;;
+esac
+])# AC_C_DOUBLE_MIDDLE_ENDIAN
+
dnl ----------------------------------------------------------------------
dnl
@@ -1337,6 +1435,14 @@ if test "$ac_cv_c_bigendian" = "yes"; then
AC_DEFINE(ETHR_BIGENDIAN, 1, [Define if bigendian])
fi
+case X$erl_xcomp_double_middle_endian in
+ X) ;;
+ Xyes|Xno|Xunknown) ac_cv_c_double_middle_endian=$erl_xcomp_double_middle_endian;;
+ *) AC_MSG_ERROR([Bad erl_xcomp_double_middle_endian value: $erl_xcomp_double_middle_endian]);;
+esac
+
+AC_C_DOUBLE_MIDDLE_ENDIAN
+
AC_ARG_ENABLE(native-ethr-impls,
AS_HELP_STRING([--disable-native-ethr-impls],
[disable native ethread implementations]),
diff --git a/erts/configure.in b/erts/configure.in
index b3289bf84c..281f61f86e 100644
--- a/erts/configure.in
+++ b/erts/configure.in
@@ -1730,6 +1730,7 @@ case X$erl_xcomp_bigendian in
esac
AC_C_BIGENDIAN
+AC_C_DOUBLE_MIDDLE_ENDIAN
dnl fdatasync syscall (Unix only)
AC_CHECK_FUNCS([fdatasync])
@@ -1943,6 +1944,64 @@ AC_CHECK_FUNCS([openpty])
AC_CHECK_HEADERS(net/if_dl.h ifaddrs.h netpacket/packet.h)
AC_CHECK_FUNCS([getifaddrs])
+dnl Checks for variables in6addr_any and in6addr_loopback,
+dnl
+dnl They normally declared by netinet/in.h, according to POSIX,
+dnl but not on Windows 7 (Windows SDK 7.1). I would have liked
+dnl to just write AC_CHECK_DECL([in6addr_any], ...) but if doing so,
+dnl the configure check fails erroneously on Linux with the error
+dnl "cannot convert to a pointer type", on a line looking like
+dnl "char *p = (char *) in6addr_any;", so work around that
+dnl with some more code.
+AC_CACHE_CHECK(
+ [whether in6addr_any is declared],
+ [erts_cv_have_in6addr_any],
+ [AC_LINK_IFELSE(
+ [AC_LANG_PROGRAM(
+ [[
+ #include <sys/types.h>
+ #include <sys/socket.h>
+ #include <netinet/in.h>
+ ]],
+ [[printf("%d", in6addr_any.s6_addr[16]);]]
+ )],
+ [erts_cv_have_in6addr_any=yes],
+ [erts_cv_have_in6addr_any=no]
+ )]
+)
+
+case "$erts_cv_have_in6addr_any" in
+ yes)
+ AC_DEFINE([HAVE_IN6ADDR_ANY], [1],
+ [Define to 1 if you have the variable in6addr_any declared.])
+esac
+
+AC_CACHE_CHECK(
+ [whether in6addr_loopback is declared],
+ [erts_cv_have_in6addr_loopback],
+ [AC_LINK_IFELSE(
+ [AC_LANG_PROGRAM(
+ [[
+ #include <sys/types.h>
+ #include <sys/socket.h>
+ #include <netinet/in.h>
+ ]],
+ [[printf("%d", in6addr_loopback.s6_addr[16]);]]
+ )],
+ [erts_cv_have_in6addr_loopback=yes],
+ [erts_cv_have_in6addr_loopback=no]
+ )]
+)
+
+case "$erts_cv_have_in6addr_loopback" in
+ yes)
+ AC_DEFINE([HAVE_IN6ADDR_LOOPBACK], [1],
+ [Define to 1 if you have the variable in6addr_loopback declared.])
+esac
+
+AC_CHECK_DECLS([IN6ADDR_ANY_INIT, IN6ADDR_LOOPBACK_INIT], [], [],
+ [#include <netinet/in.h>])
+
dnl ----------------------------------------------------------------------
dnl Checks for features/quirks in the system that affects Erlang.
dnl ----------------------------------------------------------------------
diff --git a/erts/emulator/beam/erl_bits.c b/erts/emulator/beam/erl_bits.c
index 6f7309f493..b7c82935ff 100644
--- a/erts/emulator/beam/erl_bits.c
+++ b/erts/emulator/beam/erl_bits.c
@@ -1005,8 +1005,13 @@ erts_new_bs_put_float(Process *c_p, Eterm arg, Uint num_bits, int flags)
if (is_float(arg)) {
FloatDef *fdp = (FloatDef*)(float_val(arg) + 1);
+#ifdef DOUBLE_MIDDLE_ENDIAN
+ a = fdp->fw[1];
+ b = fdp->fw[0];
+#else
a = fdp->fw[0];
b = fdp->fw[1];
+#endif
} else if (is_small(arg)) {
u.f64 = (double) signed_val(arg);
a = u.i32[0];
@@ -1015,8 +1020,13 @@ erts_new_bs_put_float(Process *c_p, Eterm arg, Uint num_bits, int flags)
if (big_to_double(arg, &u.f64) < 0) {
return 0;
}
+#ifdef DOUBLE_MIDDLE_ENDIAN
+ a = u.i32[1];
+ b = u.i32[0];
+#else
a = u.i32[0];
b = u.i32[1];
+#endif
} else {
return 0;
}
diff --git a/erts/emulator/drivers/common/inet_drv.c b/erts/emulator/drivers/common/inet_drv.c
index 76a9b55179..8f4fff0f40 100644
--- a/erts/emulator/drivers/common/inet_drv.c
+++ b/erts/emulator/drivers/common/inet_drv.c
@@ -1228,6 +1228,27 @@ struct erl_drv_entry inet_driver_entry =
NULL,
};
+#if HAVE_IN6
+# if ! defined(HAVE_IN6ADDR_ANY) || ! HAVE_IN6ADDR_ANY
+# if HAVE_DECL_IN6ADDR_ANY_INIT
+static const struct in6_addr in6addr_any = { { IN6ADDR_ANY_INIT } };
+# else
+static const struct in6_addr in6addr_any =
+ { { { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 } } };
+# endif /* HAVE_IN6ADDR_ANY_INIT */
+# endif /* ! HAVE_DECL_IN6ADDR_ANY */
+
+# if ! defined(HAVE_IN6ADDR_LOOPBACK) || ! HAVE_IN6ADDR_LOOPBACK
+# if HAVE_DECL_IN6ADDR_LOOPBACK_INIT
+static const struct in6_addr in6addr_loopback =
+ { { IN6ADDR_LOOPBACK_INIT } };
+# else
+static const struct in6_addr in6addr_loopback =
+ { { { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1 } } };
+# endif /* HAVE_IN6ADDR_LOOPBACk_INIT */
+# endif /* ! HAVE_DECL_IN6ADDR_LOOPBACK */
+#endif /* HAVE_IN6 */
+
/* XXX: is this a driver interface function ??? */
void erl_exit(int n, char*, ...);
@@ -3706,6 +3727,9 @@ static char* inet_set_address(int family, inet_address* dst,
if ((family == AF_INET) && (*len >= 2+4)) {
sys_memzero((char*)dst, sizeof(struct sockaddr_in));
port = get_int16(src);
+#ifndef NO_SA_LEN
+ dst->sai.sin_len = sizeof(struct sockaddr_in);
+#endif
dst->sai.sin_family = family;
dst->sai.sin_port = sock_htons(port);
sys_memcpy(&dst->sai.sin_addr, src+2, 4);
@@ -3716,6 +3740,9 @@ static char* inet_set_address(int family, inet_address* dst,
else if ((family == AF_INET6) && (*len >= 2+16)) {
sys_memzero((char*)dst, sizeof(struct sockaddr_in6));
port = get_int16(src);
+#ifndef NO_SA_LEN
+ dst->sai6.sin6_len = sizeof(struct sockaddr_in6);
+#endif
dst->sai6.sin6_family = family;
dst->sai6.sin6_port = sock_htons(port);
dst->sai6.sin6_flowinfo = 0; /* XXX this may be set as well ?? */
@@ -3726,7 +3753,7 @@ static char* inet_set_address(int family, inet_address* dst,
#endif
return NULL;
}
-#ifdef HAVE_SCTP
+
/*
** Set an inaddr structure, address family comes from source data,
** or from argument if source data specifies constant address.
@@ -3770,6 +3797,9 @@ static char *inet_set_faddress(int family, inet_address* dst,
return NULL;
}
sys_memzero((char*)dst, sizeof(struct sockaddr_in));
+#ifndef NO_SA_LEN
+ dst->sai.sin_len = sizeof(struct sockaddr_in6);
+#endif
dst->sai.sin_family = family;
dst->sai.sin_port = sock_htons(port);
dst->sai.sin_addr.s_addr = addr.s_addr;
@@ -3789,6 +3819,9 @@ static char *inet_set_faddress(int family, inet_address* dst,
return NULL;
}
sys_memzero((char*)dst, sizeof(struct sockaddr_in6));
+#ifndef NO_SA_LEN
+ dst->sai6.sin6_len = sizeof(struct sockaddr_in6);
+#endif
dst->sai6.sin6_family = family;
dst->sai6.sin6_port = sock_htons(port);
dst->sai6.sin6_flowinfo = 0; /* XXX this may be set as well ?? */
@@ -3806,7 +3839,7 @@ static char *inet_set_faddress(int family, inet_address* dst,
}
return inet_set_address(family, dst, src, len);
}
-#endif /* HAVE_SCTP */
+
/* Get a inaddr structure
** src = inaddr structure
@@ -7771,7 +7804,7 @@ static ErlDrvSSizeT inet_ctl(inet_descriptor* desc, int cmd, char* buf,
if (desc->state != INET_STATE_OPEN)
return ctl_xerror(EXBADPORT, rbuf, rsize);
- if (inet_set_address(desc->sfamily, &local, buf, &len) == NULL)
+ if (inet_set_faddress(desc->sfamily, &local, buf, &len) == NULL)
return ctl_error(EINVAL, rbuf, rsize);
if (IS_SOCKET_ERROR(sock_bind(desc->s,(struct sockaddr*) &local, len)))
@@ -10136,12 +10169,13 @@ static ErlDrvSSizeT packet_inet_ctl(ErlDrvData e, unsigned int cmd, char* buf,
case SCTP_REQ_BINDX:
{ /* Multi-homing bind for SCTP: */
- /* Construct the list of addresses we bind to. The curr limit is
- 256 addrs. Buff structure: Flags(1), ListItem,...:
+ /* Add additional addresses by calling sctp_bindx with one address
+ at a time, since this is what some OSes promise will work.
+ Buff structure: Flags(1), ListItem,...:
*/
- struct sockaddr addrs[256];
+ inet_address addr;
char* curr;
- int add_flag, n, rflag;
+ int add_flag, rflag;
if (!IS_SCTP(desc))
return ctl_xerror(EXBADPORT, rbuf, rsize);
@@ -10150,27 +10184,22 @@ static ErlDrvSSizeT packet_inet_ctl(ErlDrvData e, unsigned int cmd, char* buf,
add_flag = get_int8(curr);
curr++;
- for(n=0; n < 256 && curr < buf+len; n++)
+ /* Make the real flags: */
+ rflag = add_flag ? SCTP_BINDX_ADD_ADDR : SCTP_BINDX_REM_ADDR;
+
+ while (curr < buf+len)
{
- /* List item format: Port(2), IP(4|16) -- compatible with
- "inet_set_address": */
- inet_address tmp;
+ /* List item format: see "inet_set_faddress": */
ErlDrvSizeT alen = buf + len - curr;
- curr = inet_set_address(desc->sfamily, &tmp, curr, &alen);
+ curr = inet_set_faddress(desc->sfamily, &addr, curr, &alen);
if (curr == NULL)
return ctl_error(EINVAL, rbuf, rsize);
- /* Now: we need to squeeze "tmp" into the size of "sockaddr",
- which is smaller than "tmp" for IPv6 (extra IN6 info will
- be cut off): */
- memcpy(addrs + n, &tmp, sizeof(struct sockaddr));
+ /* Invoke the call: */
+ if (p_sctp_bindx(desc->s, (struct sockaddr *)&addr, 1,
+ rflag) < 0)
+ return ctl_error(sock_errno(), rbuf, rsize);
}
- /* Make the real flags: */
- rflag = add_flag ? SCTP_BINDX_ADD_ADDR : SCTP_BINDX_REM_ADDR;
-
- /* Invoke the call: */
- if (p_sctp_bindx(desc->s, addrs, n, rflag) < 0)
- return ctl_error(sock_errno(), rbuf, rsize);
desc->state = INET_STATE_BOUND;
diff --git a/erts/emulator/hipe/hipe_bif0.c b/erts/emulator/hipe/hipe_bif0.c
index af593229c0..23ced284bf 100644
--- a/erts/emulator/hipe/hipe_bif0.c
+++ b/erts/emulator/hipe/hipe_bif0.c
@@ -1755,6 +1755,41 @@ BIF_RETTYPE hipe_bifs_mark_referred_from_1(BIF_ALIST_1) /* get_refs_from */
BIF_RET(NIL);
}
+/* Called by init:restart after unloading all hipe compiled modules
+ * to work around bug causing execution of deallocated beam code.
+ * Can be removed when delete/purge of native modules works better.
+ * Test: Do init:restart in debug compiled vm with hipe compiled kernel.
+ */
+static void hipe_purge_all_refs(void)
+{
+ struct hipe_mfa_info **bucket;
+ unsigned int i, nrbuckets;
+
+ hipe_mfa_info_table_lock();
+
+ bucket = hipe_mfa_info_table.bucket;
+ nrbuckets = 1 << hipe_mfa_info_table.log2size;
+ for (i = 0; i < nrbuckets; ++i) {
+ while (bucket[i] != NULL) {
+ struct hipe_mfa_info* mfa = bucket[i];
+ bucket[i] = mfa->bucket.next;
+
+ while (mfa->refers_to) {
+ struct hipe_mfa_info_list *to = mfa->refers_to;
+ mfa->refers_to = to->next;
+ erts_free(ERTS_ALC_T_HIPE, to);
+ }
+ while (mfa->referred_from) {
+ struct ref* from = mfa->referred_from;
+ mfa->referred_from = from->next;
+ erts_free(ERTS_ALC_T_HIPE, from);
+ }
+ erts_free(ERTS_ALC_T_HIPE, mfa);
+ }
+ }
+ hipe_mfa_info_table_unlock();
+}
+
BIF_RETTYPE hipe_bifs_remove_refs_from_1(BIF_ALIST_1)
{
struct mfa mfa;
@@ -1762,6 +1797,11 @@ BIF_RETTYPE hipe_bifs_remove_refs_from_1(BIF_ALIST_1)
struct hipe_mfa_info_list *refers_to, *tmp_refers_to;
struct ref **prev, *ref;
+ if (BIF_ARG_1 == am_all) {
+ hipe_purge_all_refs();
+ BIF_RET(NIL);
+ }
+
if (!term_to_mfa(BIF_ARG_1, &mfa))
BIF_ERROR(BIF_P, BADARG);
hipe_mfa_info_table_lock();
@@ -1799,6 +1839,7 @@ BIF_RETTYPE hipe_bifs_remove_refs_from_1(BIF_ALIST_1)
BIF_RET(NIL);
}
+
/* redirect_referred_from(CalleeMFA)
* Redirect all pending-redirect refs in CalleeMFA's referred_from.
* Then remove any pending-redirect && pending-remove refs from CalleeMFA's referred_from.
diff --git a/erts/emulator/test/float_SUITE.erl b/erts/emulator/test/float_SUITE.erl
index 8e6923ce9f..abe01bf5ec 100644
--- a/erts/emulator/test/float_SUITE.erl
+++ b/erts/emulator/test/float_SUITE.erl
@@ -25,7 +25,7 @@
init_per_group/2,end_per_group/2,
init_per_testcase/2,end_per_testcase/2,
fpe/1,fp_drv/1,fp_drv_thread/1,denormalized/1,match/1,
- bad_float_unpack/1,cmp_zero/1, cmp_integer/1, cmp_bignum/1]).
+ bad_float_unpack/1, write/1, cmp_zero/1, cmp_integer/1, cmp_bignum/1]).
-export([otp_7178/1]).
-export([hidden_inf/1]).
@@ -42,7 +42,7 @@ suite() -> [{ct_hooks,[ts_install_cth]}].
all() ->
[fpe, fp_drv, fp_drv_thread, otp_7178, denormalized,
- match, bad_float_unpack, {group, comparison}
+ match, bad_float_unpack, write, {group, comparison}
,hidden_inf
].
@@ -190,6 +190,11 @@ bad_float_unpack(Config) when is_list(Config) ->
bad_float_unpack_match(<<F:64/float>>) -> F;
bad_float_unpack_match(<<I:64/integer-signed>>) -> I.
+%% Exposes endianness issues.
+
+write(Config) when is_list(Config) ->
+ "1.0" = io_lib:write(1.0).
+
cmp_zero(_Config) ->
cmp(0.5e-323,0).
diff --git a/erts/emulator/utils/make_preload b/erts/emulator/utils/make_preload
index 13019d4062..4e329b24f8 100755
--- a/erts/emulator/utils/make_preload
+++ b/erts/emulator/utils/make_preload
@@ -87,6 +87,7 @@ foreach $file (@ARGV) {
my $comment = '';
open(FILE, $file) or error("failed to read $file: $!");
+ binmode(FILE);
$_ = <FILE>;
$_ = beam_strip($_);
close(FILE);
diff --git a/erts/etc/common/erlc.c b/erts/etc/common/erlc.c
index 0223cc5274..9f05213c95 100644
--- a/erts/etc/common/erlc.c
+++ b/erts/etc/common/erlc.c
@@ -335,7 +335,7 @@ main(int argc, char** argv)
/* Push the following options:
* o makedep_phony
*/
- buf = strsave("makedep_add_missing");
+ buf = strsave("makedep_phony");
PUSH2("@option", buf);
break;
default:
diff --git a/erts/lib_src/common/erl_printf_format.c b/erts/lib_src/common/erl_printf_format.c
index 473791dce4..71d2aa35d0 100644
--- a/erts/lib_src/common/erl_printf_format.c
+++ b/erts/lib_src/common/erl_printf_format.c
@@ -457,6 +457,15 @@ static int fmt_double(fmtfn_t fn,void*arg,double val,
return res;
}
+/* strnlen doesn't exist everywhere */
+static size_t my_strnlen(const char *s, size_t maxlen)
+{
+ size_t i = 0;
+ while (i < maxlen && s[i] != '\0')
+ i++;
+ return i;
+}
+
int erts_printf_format(fmtfn_t fn, void* arg, char* fmt, va_list ap)
{
char* ptr0 = fmt;
@@ -771,9 +780,7 @@ int erts_printf_format(fmtfn_t fn, void* arg, char* fmt, va_list ap)
case FMTC_s: {
char* str = va_arg(ap,char*);
- int len = strlen(str);
- if (precision >= 0 && precision < len)
- len = precision;
+ int len = (precision >= 0) ? my_strnlen(str,precision) : strlen(str);
if (width > 0 && !(fmt & FMTF_adj)) {
if (width > len)
BLANKS(fn, arg, width - len, count);
diff --git a/erts/preloaded/ebin/init.beam b/erts/preloaded/ebin/init.beam
index 9f2d369af2..b588b450c9 100644
--- a/erts/preloaded/ebin/init.beam
+++ b/erts/preloaded/ebin/init.beam
Binary files differ
diff --git a/erts/preloaded/ebin/prim_inet.beam b/erts/preloaded/ebin/prim_inet.beam
index b2f3ab6c5b..ad49f5e892 100644
--- a/erts/preloaded/ebin/prim_inet.beam
+++ b/erts/preloaded/ebin/prim_inet.beam
Binary files differ
diff --git a/erts/preloaded/src/init.erl b/erts/preloaded/src/init.erl
index 708991e261..185b62a899 100644
--- a/erts/preloaded/src/init.erl
+++ b/erts/preloaded/src/init.erl
@@ -635,8 +635,16 @@ do_unload([M|Mods]) ->
catch erlang:purge_module(M),
do_unload(Mods);
do_unload([]) ->
+ purge_all_hipe_refs(),
ok.
+purge_all_hipe_refs() ->
+ case erlang:system_info(hipe_architecture) of
+ undefined -> ok;
+ _ -> hipe_bifs:remove_refs_from(all)
+ end.
+
+
sub([H|T],L) -> sub(T,del(H,L));
sub([],L) -> L.
diff --git a/erts/preloaded/src/prim_inet.erl b/erts/preloaded/src/prim_inet.erl
index 846ae97ed2..91fcd3ac82 100644
--- a/erts/preloaded/src/prim_inet.erl
+++ b/erts/preloaded/src/prim_inet.erl
@@ -184,7 +184,7 @@ close_pend_loop(S, N) ->
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
bind(S,IP,Port) when is_port(S), is_integer(Port), Port >= 0, Port =< 65535 ->
- case ctl_cmd(S,?INET_REQ_BIND,[?int16(Port),ip_to_bytes(IP)]) of
+ case ctl_cmd(S,?INET_REQ_BIND,enc_value(set, addr, {IP,Port})) of
{ok, [P1,P0]} -> {ok, ?u16(P1, P0)};
{error,_}=Error -> Error
end;
@@ -206,10 +206,10 @@ bindx(S, AddFlag, Addrs) ->
case getprotocol(S) of
sctp ->
%% Really multi-homed "bindx". Stringified args:
- %% [AddFlag, (Port, IP)+]:
+ %% [AddFlag, (AddrBytes see enc_value_2(addr,X))+]:
Args =
[?int8(AddFlag)|
- [[?int16(Port)|ip_to_bytes(IP)] ||
+ [enc_value(set, addr, {IP,Port}) ||
{IP, Port} <- Addrs]],
case ctl_cmd(S, ?SCTP_REQ_BINDX, Args) of
{ok,_} -> {ok, S};
diff --git a/lib/common_test/doc/src/ct_run.xml b/lib/common_test/doc/src/ct_run.xml
index 078b9b958c..df1defa664 100644
--- a/lib/common_test/doc/src/ct_run.xml
+++ b/lib/common_test/doc/src/ct_run.xml
@@ -88,11 +88,13 @@
[-step [config | keep_inactive]]
[-config ConfigFile1 ConfigFile2 .. ConfigFileN]
[-userconfig CallbackModule1 ConfigString1 and CallbackModule2
- ConfigString2 and .. and CallbackModuleN ConfigStringN]
+ ConfigString2 and .. CallbackModuleN ConfigStringN]
[-decrypt_key Key] | [-decrypt_file KeyFile]
[-label Label]
[-logdir LogDir]
[-logopts LogOpts]
+ [-verbosity GenVLevel | [Category1 VLevel1 and
+ Category2 VLevel2 and .. CategoryN VLevelN]]
[-silent_connections [ConnType1 ConnType2 .. ConnTypeN]]
[-stylesheet CSSFile]
[-cover CoverCfgFile]
@@ -107,7 +109,10 @@
[-repeat N [-force_stop]] |
[-duration HHMMSS [-force_stop]] |
[-until [YYMoMoDD]HHMMSS [-force_stop]]
- [-basic_html]</pre>
+ [-basic_html]
+ [-ct_hooks CTHModule1 CTHOpts1 and CTHModule2 CTHOpts2 and ..
+ CTHModuleN CTHOptsN]
+ </pre>
</section>
<section>
<title>Run tests using test specification</title>
@@ -120,6 +125,8 @@
[-label Label]
[-logdir LogDir]
[-logopts LogOpts]
+ [-verbosity GenVLevel | [Category1 VLevel1 and
+ Category2 VLevel2 and .. CategoryN VLevelN]]
[-allow_user_terms]
[-silent_connections [ConnType1 ConnType2 .. ConnTypeN]]
[-stylesheet CSSFile]
@@ -135,7 +142,10 @@
[-repeat N [-force_stop]] |
[-duration HHMMSS [-force_stop]] |
[-until [YYMoMoDD]HHMMSS [-force_stop]]
- [-basic_html]</pre>
+ [-basic_html]
+ [-ct_hooks CTHModule1 CTHOpts1 and CTHModule2 CTHOpts2 and ..
+ CTHModuleN CTHOptsN]
+ </pre>
</section>
<section>
<title>Run tests in web based GUI</title>
@@ -147,6 +157,8 @@
[-userconfig CallbackModule1 ConfigString1 and CallbackModule2
ConfigString2 and .. and CallbackModuleN ConfigStringN]
[-logopts LogOpts]
+ [-verbosity GenVLevel | [Category1 VLevel1 and
+ Category2 VLevel2 and .. CategoryN VLevelN]]
[-decrypt_key Key] | [-decrypt_file KeyFile]
[-include InclDir1 InclDir2 .. InclDirN]
[-no_auto_compile]
diff --git a/lib/common_test/doc/src/run_test_chapter.xml b/lib/common_test/doc/src/run_test_chapter.xml
index 30486d3eec..bc0af63790 100644
--- a/lib/common_test/doc/src/run_test_chapter.xml
+++ b/lib/common_test/doc/src/run_test_chapter.xml
@@ -425,7 +425,7 @@
<p>Below is the test specification syntax. Test specifications can
be used to run tests both in a single test host environment and
in a distributed Common Test environment (Large Scale
- Testing). The node parameters in the init term are only
+ Testing). The node parameters in the <c>init</c> term are only
relevant in the latter (see the
<seealso marker="ct_master_chapter#test_specifications">Large
Scale Testing</seealso> chapter for information). For details on
@@ -575,9 +575,9 @@
<item>Lastly, all suites for systems t3 are to be completely skipped and this
should be explicitly noted in the log files.</item>
</list>
- <p>It is possible to specify initialization options for nodes defined in the
- test specification. Currently, there are options to start the node and/or to
- evaluate any function on the node.
+ <p>With the <c>init</c> term it's possible to specify initialization options
+ for nodes defined in the test specification. Currently, there are options
+ to start the node and/or to evaluate any function on the node.
See the <seealso marker="ct_master_chapter#ct_slave">Automatic startup of
the test target nodes</seealso> chapter for details.</p>
<p>It is possible for the user to provide a test specification that
diff --git a/lib/common_test/include/ct.hrl b/lib/common_test/include/ct.hrl
index 5a77108e1a..43b1b1c11f 100644
--- a/lib/common_test/include/ct.hrl
+++ b/lib/common_test/include/ct.hrl
@@ -19,3 +19,16 @@
-include_lib("test_server/include/test_server.hrl").
+%% the log level is used as argument to any CT logging function
+-define(MIN_IMPORTANCE, 0 ).
+-define(LOW_IMPORTANCE, 25).
+-define(STD_IMPORTANCE, 50).
+-define(HI_IMPORTANCE, 75).
+-define(MAX_IMPORTANCE, 99).
+
+%% verbosity thresholds to filter out logging printouts
+-define(MIN_VERBOSITY, 0 ). %% turn logging off
+-define(LOW_VERBOSITY, 25 ).
+-define(STD_VERBOSITY, 50 ).
+-define(HI_VERBOSITY, 75 ).
+-define(MAX_VERBOSITY, 100).
diff --git a/lib/common_test/priv/Makefile.in b/lib/common_test/priv/Makefile.in
index 8be6248e17..2f06f7df65 100644
--- a/lib/common_test/priv/Makefile.in
+++ b/lib/common_test/priv/Makefile.in
@@ -60,6 +60,7 @@ FILES = vts.tool
SCRIPTS =
IMAGES = tile1.jpg
CSS = ct_default.css
+JS = jquery-latest.js jquery.tablesorter.min.js
#
# Rules
@@ -86,11 +87,11 @@ include $(ERL_TOP)/make/otp_release_targets.mk
ifeq ($(XNIX),true)
release_spec: opt
$(INSTALL_DIR) "$(RELSYSDIR)/priv"
- $(INSTALL_DATA) $(FILES) $(IMAGES) $(CSS) "$(RELSYSDIR)/priv"
+ $(INSTALL_DATA) $(FILES) $(IMAGES) $(CSS) $(JS) "$(RELSYSDIR)/priv"
else
release_spec: opt
$(INSTALL_DIR) "$(RELSYSDIR)/priv"
- $(INSTALL_DATA) $(FILES) $(IMAGES) $(CSS) "$(RELSYSDIR)/priv"
+ $(INSTALL_DATA) $(FILES) $(IMAGES) $(CSS) $(JS) "$(RELSYSDIR)/priv"
endif
release_docs_spec:
@@ -107,6 +108,7 @@ else
FILES = vts.tool
IMAGES = tile1.jpg
CSS = ct_default.css
+JS = jquery-latest.js jquery.tablesorter.min.js
#
# Rules
@@ -126,7 +128,7 @@ include $(ERL_TOP)/make/otp_release_targets.mk
release_spec: opt
$(INSTALL_DIR) "$(RELSYSDIR)/priv"
- $(INSTALL_DATA) $(FILES) $(IMAGES) $(CSS) "$(RELSYSDIR)/priv"
+ $(INSTALL_DATA) $(FILES) $(IMAGES) $(CSS) $(JS) "$(RELSYSDIR)/priv"
release_docs_spec:
diff --git a/lib/common_test/priv/ct_default.css b/lib/common_test/priv/ct_default.css
index 8ae6990cd8..1188f8f676 100644
--- a/lib/common_test/priv/ct_default.css
+++ b/lib/common_test/priv/ct_default.css
@@ -136,7 +136,18 @@ th {
}
thead th {
- background: #2C5755; text-align: center;
+ background: #3F3F3F; color: #fff;
+ font-family: arial, sans-serif; font-size: 120%;
+ letter-spacing: -0.5px;
+ font-weight: bold; text-align: center;
+ padding-right: .5em; vertical-align: top;
+ text-decoration: underline;
+}
+
+tfoot td {
+ font-family: arial, sans-serif; font-size: 110%;
+ letter-spacing: -0.5px;
+ font-weight: bold;
}
.odd td {
@@ -167,10 +178,6 @@ th a, td a:active {
color: #85ABD5;
}
-tfoot th, tfoot td {
- background: #3F3F3F; color: #fff;
-}
-
th + td {
padding-left: .5em;
}
diff --git a/lib/common_test/priv/jquery-latest.js b/lib/common_test/priv/jquery-latest.js
new file mode 100644
index 0000000000..ac7e7009dc
--- /dev/null
+++ b/lib/common_test/priv/jquery-latest.js
@@ -0,0 +1,154 @@
+/*!
+ * jQuery JavaScript Library v1.4.2
+ * http://jquery.com/
+ *
+ * Copyright 2010, John Resig
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * Includes Sizzle.js
+ * http://sizzlejs.com/
+ * Copyright 2010, The Dojo Foundation
+ * Released under the MIT, BSD, and GPL Licenses.
+ *
+ * Date: Sat Feb 13 22:33:48 2010 -0500
+ */
+(function(A,w){function ma(){if(!c.isReady){try{s.documentElement.doScroll("left")}catch(a){setTimeout(ma,1);return}c.ready()}}function Qa(a,b){b.src?c.ajax({url:b.src,async:false,dataType:"script"}):c.globalEval(b.text||b.textContent||b.innerHTML||"");b.parentNode&&b.parentNode.removeChild(b)}function X(a,b,d,f,e,j){var i=a.length;if(typeof b==="object"){for(var o in b)X(a,o,b[o],f,e,d);return a}if(d!==w){f=!j&&f&&c.isFunction(d);for(o=0;o<i;o++)e(a[o],b,f?d.call(a[o],o,e(a[o],b)):d,j);return a}return i?
+e(a[0],b):w}function J(){return(new Date).getTime()}function Y(){return false}function Z(){return true}function na(a,b,d){d[0].type=a;return c.event.handle.apply(b,d)}function oa(a){var b,d=[],f=[],e=arguments,j,i,o,k,n,r;i=c.data(this,"events");if(!(a.liveFired===this||!i||!i.live||a.button&&a.type==="click")){a.liveFired=this;var u=i.live.slice(0);for(k=0;k<u.length;k++){i=u[k];i.origType.replace(O,"")===a.type?f.push(i.selector):u.splice(k--,1)}j=c(a.target).closest(f,a.currentTarget);n=0;for(r=
+j.length;n<r;n++)for(k=0;k<u.length;k++){i=u[k];if(j[n].selector===i.selector){o=j[n].elem;f=null;if(i.preType==="mouseenter"||i.preType==="mouseleave")f=c(a.relatedTarget).closest(i.selector)[0];if(!f||f!==o)d.push({elem:o,handleObj:i})}}n=0;for(r=d.length;n<r;n++){j=d[n];a.currentTarget=j.elem;a.data=j.handleObj.data;a.handleObj=j.handleObj;if(j.handleObj.origHandler.apply(j.elem,e)===false){b=false;break}}return b}}function pa(a,b){return"live."+(a&&a!=="*"?a+".":"")+b.replace(/\./g,"`").replace(/ /g,
+"&")}function qa(a){return!a||!a.parentNode||a.parentNode.nodeType===11}function ra(a,b){var d=0;b.each(function(){if(this.nodeName===(a[d]&&a[d].nodeName)){var f=c.data(a[d++]),e=c.data(this,f);if(f=f&&f.events){delete e.handle;e.events={};for(var j in f)for(var i in f[j])c.event.add(this,j,f[j][i],f[j][i].data)}}})}function sa(a,b,d){var f,e,j;b=b&&b[0]?b[0].ownerDocument||b[0]:s;if(a.length===1&&typeof a[0]==="string"&&a[0].length<512&&b===s&&!ta.test(a[0])&&(c.support.checkClone||!ua.test(a[0]))){e=
+true;if(j=c.fragments[a[0]])if(j!==1)f=j}if(!f){f=b.createDocumentFragment();c.clean(a,b,f,d)}if(e)c.fragments[a[0]]=j?f:1;return{fragment:f,cacheable:e}}function K(a,b){var d={};c.each(va.concat.apply([],va.slice(0,b)),function(){d[this]=a});return d}function wa(a){return"scrollTo"in a&&a.document?a:a.nodeType===9?a.defaultView||a.parentWindow:false}var c=function(a,b){return new c.fn.init(a,b)},Ra=A.jQuery,Sa=A.$,s=A.document,T,Ta=/^[^<]*(<[\w\W]+>)[^>]*$|^#([\w-]+)$/,Ua=/^.[^:#\[\.,]*$/,Va=/\S/,
+Wa=/^(\s|\u00A0)+|(\s|\u00A0)+$/g,Xa=/^<(\w+)\s*\/?>(?:<\/\1>)?$/,P=navigator.userAgent,xa=false,Q=[],L,$=Object.prototype.toString,aa=Object.prototype.hasOwnProperty,ba=Array.prototype.push,R=Array.prototype.slice,ya=Array.prototype.indexOf;c.fn=c.prototype={init:function(a,b){var d,f;if(!a)return this;if(a.nodeType){this.context=this[0]=a;this.length=1;return this}if(a==="body"&&!b){this.context=s;this[0]=s.body;this.selector="body";this.length=1;return this}if(typeof a==="string")if((d=Ta.exec(a))&&
+(d[1]||!b))if(d[1]){f=b?b.ownerDocument||b:s;if(a=Xa.exec(a))if(c.isPlainObject(b)){a=[s.createElement(a[1])];c.fn.attr.call(a,b,true)}else a=[f.createElement(a[1])];else{a=sa([d[1]],[f]);a=(a.cacheable?a.fragment.cloneNode(true):a.fragment).childNodes}return c.merge(this,a)}else{if(b=s.getElementById(d[2])){if(b.id!==d[2])return T.find(a);this.length=1;this[0]=b}this.context=s;this.selector=a;return this}else if(!b&&/^\w+$/.test(a)){this.selector=a;this.context=s;a=s.getElementsByTagName(a);return c.merge(this,
+a)}else return!b||b.jquery?(b||T).find(a):c(b).find(a);else if(c.isFunction(a))return T.ready(a);if(a.selector!==w){this.selector=a.selector;this.context=a.context}return c.makeArray(a,this)},selector:"",jquery:"1.4.2",length:0,size:function(){return this.length},toArray:function(){return R.call(this,0)},get:function(a){return a==null?this.toArray():a<0?this.slice(a)[0]:this[a]},pushStack:function(a,b,d){var f=c();c.isArray(a)?ba.apply(f,a):c.merge(f,a);f.prevObject=this;f.context=this.context;if(b===
+"find")f.selector=this.selector+(this.selector?" ":"")+d;else if(b)f.selector=this.selector+"."+b+"("+d+")";return f},each:function(a,b){return c.each(this,a,b)},ready:function(a){c.bindReady();if(c.isReady)a.call(s,c);else Q&&Q.push(a);return this},eq:function(a){return a===-1?this.slice(a):this.slice(a,+a+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(R.apply(this,arguments),"slice",R.call(arguments).join(","))},map:function(a){return this.pushStack(c.map(this,
+function(b,d){return a.call(b,d,b)}))},end:function(){return this.prevObject||c(null)},push:ba,sort:[].sort,splice:[].splice};c.fn.init.prototype=c.fn;c.extend=c.fn.extend=function(){var a=arguments[0]||{},b=1,d=arguments.length,f=false,e,j,i,o;if(typeof a==="boolean"){f=a;a=arguments[1]||{};b=2}if(typeof a!=="object"&&!c.isFunction(a))a={};if(d===b){a=this;--b}for(;b<d;b++)if((e=arguments[b])!=null)for(j in e){i=a[j];o=e[j];if(a!==o)if(f&&o&&(c.isPlainObject(o)||c.isArray(o))){i=i&&(c.isPlainObject(i)||
+c.isArray(i))?i:c.isArray(o)?[]:{};a[j]=c.extend(f,i,o)}else if(o!==w)a[j]=o}return a};c.extend({noConflict:function(a){A.$=Sa;if(a)A.jQuery=Ra;return c},isReady:false,ready:function(){if(!c.isReady){if(!s.body)return setTimeout(c.ready,13);c.isReady=true;if(Q){for(var a,b=0;a=Q[b++];)a.call(s,c);Q=null}c.fn.triggerHandler&&c(s).triggerHandler("ready")}},bindReady:function(){if(!xa){xa=true;if(s.readyState==="complete")return c.ready();if(s.addEventListener){s.addEventListener("DOMContentLoaded",
+L,false);A.addEventListener("load",c.ready,false)}else if(s.attachEvent){s.attachEvent("onreadystatechange",L);A.attachEvent("onload",c.ready);var a=false;try{a=A.frameElement==null}catch(b){}s.documentElement.doScroll&&a&&ma()}}},isFunction:function(a){return $.call(a)==="[object Function]"},isArray:function(a){return $.call(a)==="[object Array]"},isPlainObject:function(a){if(!a||$.call(a)!=="[object Object]"||a.nodeType||a.setInterval)return false;if(a.constructor&&!aa.call(a,"constructor")&&!aa.call(a.constructor.prototype,
+"isPrototypeOf"))return false;var b;for(b in a);return b===w||aa.call(a,b)},isEmptyObject:function(a){for(var b in a)return false;return true},error:function(a){throw a;},parseJSON:function(a){if(typeof a!=="string"||!a)return null;a=c.trim(a);if(/^[\],:{}\s]*$/.test(a.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,"@").replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,"]").replace(/(?:^|:|,)(?:\s*\[)+/g,"")))return A.JSON&&A.JSON.parse?A.JSON.parse(a):(new Function("return "+
+a))();else c.error("Invalid JSON: "+a)},noop:function(){},globalEval:function(a){if(a&&Va.test(a)){var b=s.getElementsByTagName("head")[0]||s.documentElement,d=s.createElement("script");d.type="text/javascript";if(c.support.scriptEval)d.appendChild(s.createTextNode(a));else d.text=a;b.insertBefore(d,b.firstChild);b.removeChild(d)}},nodeName:function(a,b){return a.nodeName&&a.nodeName.toUpperCase()===b.toUpperCase()},each:function(a,b,d){var f,e=0,j=a.length,i=j===w||c.isFunction(a);if(d)if(i)for(f in a){if(b.apply(a[f],
+d)===false)break}else for(;e<j;){if(b.apply(a[e++],d)===false)break}else if(i)for(f in a){if(b.call(a[f],f,a[f])===false)break}else for(d=a[0];e<j&&b.call(d,e,d)!==false;d=a[++e]);return a},trim:function(a){return(a||"").replace(Wa,"")},makeArray:function(a,b){b=b||[];if(a!=null)a.length==null||typeof a==="string"||c.isFunction(a)||typeof a!=="function"&&a.setInterval?ba.call(b,a):c.merge(b,a);return b},inArray:function(a,b){if(b.indexOf)return b.indexOf(a);for(var d=0,f=b.length;d<f;d++)if(b[d]===
+a)return d;return-1},merge:function(a,b){var d=a.length,f=0;if(typeof b.length==="number")for(var e=b.length;f<e;f++)a[d++]=b[f];else for(;b[f]!==w;)a[d++]=b[f++];a.length=d;return a},grep:function(a,b,d){for(var f=[],e=0,j=a.length;e<j;e++)!d!==!b(a[e],e)&&f.push(a[e]);return f},map:function(a,b,d){for(var f=[],e,j=0,i=a.length;j<i;j++){e=b(a[j],j,d);if(e!=null)f[f.length]=e}return f.concat.apply([],f)},guid:1,proxy:function(a,b,d){if(arguments.length===2)if(typeof b==="string"){d=a;a=d[b];b=w}else if(b&&
+!c.isFunction(b)){d=b;b=w}if(!b&&a)b=function(){return a.apply(d||this,arguments)};if(a)b.guid=a.guid=a.guid||b.guid||c.guid++;return b},uaMatch:function(a){a=a.toLowerCase();a=/(webkit)[ \/]([\w.]+)/.exec(a)||/(opera)(?:.*version)?[ \/]([\w.]+)/.exec(a)||/(msie) ([\w.]+)/.exec(a)||!/compatible/.test(a)&&/(mozilla)(?:.*? rv:([\w.]+))?/.exec(a)||[];return{browser:a[1]||"",version:a[2]||"0"}},browser:{}});P=c.uaMatch(P);if(P.browser){c.browser[P.browser]=true;c.browser.version=P.version}if(c.browser.webkit)c.browser.safari=
+true;if(ya)c.inArray=function(a,b){return ya.call(b,a)};T=c(s);if(s.addEventListener)L=function(){s.removeEventListener("DOMContentLoaded",L,false);c.ready()};else if(s.attachEvent)L=function(){if(s.readyState==="complete"){s.detachEvent("onreadystatechange",L);c.ready()}};(function(){c.support={};var a=s.documentElement,b=s.createElement("script"),d=s.createElement("div"),f="script"+J();d.style.display="none";d.innerHTML=" <link/><table></table><a href='/a' style='color:red;float:left;opacity:.55;'>a</a><input type='checkbox'/>";
+var e=d.getElementsByTagName("*"),j=d.getElementsByTagName("a")[0];if(!(!e||!e.length||!j)){c.support={leadingWhitespace:d.firstChild.nodeType===3,tbody:!d.getElementsByTagName("tbody").length,htmlSerialize:!!d.getElementsByTagName("link").length,style:/red/.test(j.getAttribute("style")),hrefNormalized:j.getAttribute("href")==="/a",opacity:/^0.55$/.test(j.style.opacity),cssFloat:!!j.style.cssFloat,checkOn:d.getElementsByTagName("input")[0].value==="on",optSelected:s.createElement("select").appendChild(s.createElement("option")).selected,
+parentNode:d.removeChild(d.appendChild(s.createElement("div"))).parentNode===null,deleteExpando:true,checkClone:false,scriptEval:false,noCloneEvent:true,boxModel:null};b.type="text/javascript";try{b.appendChild(s.createTextNode("window."+f+"=1;"))}catch(i){}a.insertBefore(b,a.firstChild);if(A[f]){c.support.scriptEval=true;delete A[f]}try{delete b.test}catch(o){c.support.deleteExpando=false}a.removeChild(b);if(d.attachEvent&&d.fireEvent){d.attachEvent("onclick",function k(){c.support.noCloneEvent=
+false;d.detachEvent("onclick",k)});d.cloneNode(true).fireEvent("onclick")}d=s.createElement("div");d.innerHTML="<input type='radio' name='radiotest' checked='checked'/>";a=s.createDocumentFragment();a.appendChild(d.firstChild);c.support.checkClone=a.cloneNode(true).cloneNode(true).lastChild.checked;c(function(){var k=s.createElement("div");k.style.width=k.style.paddingLeft="1px";s.body.appendChild(k);c.boxModel=c.support.boxModel=k.offsetWidth===2;s.body.removeChild(k).style.display="none"});a=function(k){var n=
+s.createElement("div");k="on"+k;var r=k in n;if(!r){n.setAttribute(k,"return;");r=typeof n[k]==="function"}return r};c.support.submitBubbles=a("submit");c.support.changeBubbles=a("change");a=b=d=e=j=null}})();c.props={"for":"htmlFor","class":"className",readonly:"readOnly",maxlength:"maxLength",cellspacing:"cellSpacing",rowspan:"rowSpan",colspan:"colSpan",tabindex:"tabIndex",usemap:"useMap",frameborder:"frameBorder"};var G="jQuery"+J(),Ya=0,za={};c.extend({cache:{},expando:G,noData:{embed:true,object:true,
+applet:true},data:function(a,b,d){if(!(a.nodeName&&c.noData[a.nodeName.toLowerCase()])){a=a==A?za:a;var f=a[G],e=c.cache;if(!f&&typeof b==="string"&&d===w)return null;f||(f=++Ya);if(typeof b==="object"){a[G]=f;e[f]=c.extend(true,{},b)}else if(!e[f]){a[G]=f;e[f]={}}a=e[f];if(d!==w)a[b]=d;return typeof b==="string"?a[b]:a}},removeData:function(a,b){if(!(a.nodeName&&c.noData[a.nodeName.toLowerCase()])){a=a==A?za:a;var d=a[G],f=c.cache,e=f[d];if(b){if(e){delete e[b];c.isEmptyObject(e)&&c.removeData(a)}}else{if(c.support.deleteExpando)delete a[c.expando];
+else a.removeAttribute&&a.removeAttribute(c.expando);delete f[d]}}}});c.fn.extend({data:function(a,b){if(typeof a==="undefined"&&this.length)return c.data(this[0]);else if(typeof a==="object")return this.each(function(){c.data(this,a)});var d=a.split(".");d[1]=d[1]?"."+d[1]:"";if(b===w){var f=this.triggerHandler("getData"+d[1]+"!",[d[0]]);if(f===w&&this.length)f=c.data(this[0],a);return f===w&&d[1]?this.data(d[0]):f}else return this.trigger("setData"+d[1]+"!",[d[0],b]).each(function(){c.data(this,
+a,b)})},removeData:function(a){return this.each(function(){c.removeData(this,a)})}});c.extend({queue:function(a,b,d){if(a){b=(b||"fx")+"queue";var f=c.data(a,b);if(!d)return f||[];if(!f||c.isArray(d))f=c.data(a,b,c.makeArray(d));else f.push(d);return f}},dequeue:function(a,b){b=b||"fx";var d=c.queue(a,b),f=d.shift();if(f==="inprogress")f=d.shift();if(f){b==="fx"&&d.unshift("inprogress");f.call(a,function(){c.dequeue(a,b)})}}});c.fn.extend({queue:function(a,b){if(typeof a!=="string"){b=a;a="fx"}if(b===
+w)return c.queue(this[0],a);return this.each(function(){var d=c.queue(this,a,b);a==="fx"&&d[0]!=="inprogress"&&c.dequeue(this,a)})},dequeue:function(a){return this.each(function(){c.dequeue(this,a)})},delay:function(a,b){a=c.fx?c.fx.speeds[a]||a:a;b=b||"fx";return this.queue(b,function(){var d=this;setTimeout(function(){c.dequeue(d,b)},a)})},clearQueue:function(a){return this.queue(a||"fx",[])}});var Aa=/[\n\t]/g,ca=/\s+/,Za=/\r/g,$a=/href|src|style/,ab=/(button|input)/i,bb=/(button|input|object|select|textarea)/i,
+cb=/^(a|area)$/i,Ba=/radio|checkbox/;c.fn.extend({attr:function(a,b){return X(this,a,b,true,c.attr)},removeAttr:function(a){return this.each(function(){c.attr(this,a,"");this.nodeType===1&&this.removeAttribute(a)})},addClass:function(a){if(c.isFunction(a))return this.each(function(n){var r=c(this);r.addClass(a.call(this,n,r.attr("class")))});if(a&&typeof a==="string")for(var b=(a||"").split(ca),d=0,f=this.length;d<f;d++){var e=this[d];if(e.nodeType===1)if(e.className){for(var j=" "+e.className+" ",
+i=e.className,o=0,k=b.length;o<k;o++)if(j.indexOf(" "+b[o]+" ")<0)i+=" "+b[o];e.className=c.trim(i)}else e.className=a}return this},removeClass:function(a){if(c.isFunction(a))return this.each(function(k){var n=c(this);n.removeClass(a.call(this,k,n.attr("class")))});if(a&&typeof a==="string"||a===w)for(var b=(a||"").split(ca),d=0,f=this.length;d<f;d++){var e=this[d];if(e.nodeType===1&&e.className)if(a){for(var j=(" "+e.className+" ").replace(Aa," "),i=0,o=b.length;i<o;i++)j=j.replace(" "+b[i]+" ",
+" ");e.className=c.trim(j)}else e.className=""}return this},toggleClass:function(a,b){var d=typeof a,f=typeof b==="boolean";if(c.isFunction(a))return this.each(function(e){var j=c(this);j.toggleClass(a.call(this,e,j.attr("class"),b),b)});return this.each(function(){if(d==="string")for(var e,j=0,i=c(this),o=b,k=a.split(ca);e=k[j++];){o=f?o:!i.hasClass(e);i[o?"addClass":"removeClass"](e)}else if(d==="undefined"||d==="boolean"){this.className&&c.data(this,"__className__",this.className);this.className=
+this.className||a===false?"":c.data(this,"__className__")||""}})},hasClass:function(a){a=" "+a+" ";for(var b=0,d=this.length;b<d;b++)if((" "+this[b].className+" ").replace(Aa," ").indexOf(a)>-1)return true;return false},val:function(a){if(a===w){var b=this[0];if(b){if(c.nodeName(b,"option"))return(b.attributes.value||{}).specified?b.value:b.text;if(c.nodeName(b,"select")){var d=b.selectedIndex,f=[],e=b.options;b=b.type==="select-one";if(d<0)return null;var j=b?d:0;for(d=b?d+1:e.length;j<d;j++){var i=
+e[j];if(i.selected){a=c(i).val();if(b)return a;f.push(a)}}return f}if(Ba.test(b.type)&&!c.support.checkOn)return b.getAttribute("value")===null?"on":b.value;return(b.value||"").replace(Za,"")}return w}var o=c.isFunction(a);return this.each(function(k){var n=c(this),r=a;if(this.nodeType===1){if(o)r=a.call(this,k,n.val());if(typeof r==="number")r+="";if(c.isArray(r)&&Ba.test(this.type))this.checked=c.inArray(n.val(),r)>=0;else if(c.nodeName(this,"select")){var u=c.makeArray(r);c("option",this).each(function(){this.selected=
+c.inArray(c(this).val(),u)>=0});if(!u.length)this.selectedIndex=-1}else this.value=r}})}});c.extend({attrFn:{val:true,css:true,html:true,text:true,data:true,width:true,height:true,offset:true},attr:function(a,b,d,f){if(!a||a.nodeType===3||a.nodeType===8)return w;if(f&&b in c.attrFn)return c(a)[b](d);f=a.nodeType!==1||!c.isXMLDoc(a);var e=d!==w;b=f&&c.props[b]||b;if(a.nodeType===1){var j=$a.test(b);if(b in a&&f&&!j){if(e){b==="type"&&ab.test(a.nodeName)&&a.parentNode&&c.error("type property can't be changed");
+a[b]=d}if(c.nodeName(a,"form")&&a.getAttributeNode(b))return a.getAttributeNode(b).nodeValue;if(b==="tabIndex")return(b=a.getAttributeNode("tabIndex"))&&b.specified?b.value:bb.test(a.nodeName)||cb.test(a.nodeName)&&a.href?0:w;return a[b]}if(!c.support.style&&f&&b==="style"){if(e)a.style.cssText=""+d;return a.style.cssText}e&&a.setAttribute(b,""+d);a=!c.support.hrefNormalized&&f&&j?a.getAttribute(b,2):a.getAttribute(b);return a===null?w:a}return c.style(a,b,d)}});var O=/\.(.*)$/,db=function(a){return a.replace(/[^\w\s\.\|`]/g,
+function(b){return"\\"+b})};c.event={add:function(a,b,d,f){if(!(a.nodeType===3||a.nodeType===8)){if(a.setInterval&&a!==A&&!a.frameElement)a=A;var e,j;if(d.handler){e=d;d=e.handler}if(!d.guid)d.guid=c.guid++;if(j=c.data(a)){var i=j.events=j.events||{},o=j.handle;if(!o)j.handle=o=function(){return typeof c!=="undefined"&&!c.event.triggered?c.event.handle.apply(o.elem,arguments):w};o.elem=a;b=b.split(" ");for(var k,n=0,r;k=b[n++];){j=e?c.extend({},e):{handler:d,data:f};if(k.indexOf(".")>-1){r=k.split(".");
+k=r.shift();j.namespace=r.slice(0).sort().join(".")}else{r=[];j.namespace=""}j.type=k;j.guid=d.guid;var u=i[k],z=c.event.special[k]||{};if(!u){u=i[k]=[];if(!z.setup||z.setup.call(a,f,r,o)===false)if(a.addEventListener)a.addEventListener(k,o,false);else a.attachEvent&&a.attachEvent("on"+k,o)}if(z.add){z.add.call(a,j);if(!j.handler.guid)j.handler.guid=d.guid}u.push(j);c.event.global[k]=true}a=null}}},global:{},remove:function(a,b,d,f){if(!(a.nodeType===3||a.nodeType===8)){var e,j=0,i,o,k,n,r,u,z=c.data(a),
+C=z&&z.events;if(z&&C){if(b&&b.type){d=b.handler;b=b.type}if(!b||typeof b==="string"&&b.charAt(0)==="."){b=b||"";for(e in C)c.event.remove(a,e+b)}else{for(b=b.split(" ");e=b[j++];){n=e;i=e.indexOf(".")<0;o=[];if(!i){o=e.split(".");e=o.shift();k=new RegExp("(^|\\.)"+c.map(o.slice(0).sort(),db).join("\\.(?:.*\\.)?")+"(\\.|$)")}if(r=C[e])if(d){n=c.event.special[e]||{};for(B=f||0;B<r.length;B++){u=r[B];if(d.guid===u.guid){if(i||k.test(u.namespace)){f==null&&r.splice(B--,1);n.remove&&n.remove.call(a,u)}if(f!=
+null)break}}if(r.length===0||f!=null&&r.length===1){if(!n.teardown||n.teardown.call(a,o)===false)Ca(a,e,z.handle);delete C[e]}}else for(var B=0;B<r.length;B++){u=r[B];if(i||k.test(u.namespace)){c.event.remove(a,n,u.handler,B);r.splice(B--,1)}}}if(c.isEmptyObject(C)){if(b=z.handle)b.elem=null;delete z.events;delete z.handle;c.isEmptyObject(z)&&c.removeData(a)}}}}},trigger:function(a,b,d,f){var e=a.type||a;if(!f){a=typeof a==="object"?a[G]?a:c.extend(c.Event(e),a):c.Event(e);if(e.indexOf("!")>=0){a.type=
+e=e.slice(0,-1);a.exclusive=true}if(!d){a.stopPropagation();c.event.global[e]&&c.each(c.cache,function(){this.events&&this.events[e]&&c.event.trigger(a,b,this.handle.elem)})}if(!d||d.nodeType===3||d.nodeType===8)return w;a.result=w;a.target=d;b=c.makeArray(b);b.unshift(a)}a.currentTarget=d;(f=c.data(d,"handle"))&&f.apply(d,b);f=d.parentNode||d.ownerDocument;try{if(!(d&&d.nodeName&&c.noData[d.nodeName.toLowerCase()]))if(d["on"+e]&&d["on"+e].apply(d,b)===false)a.result=false}catch(j){}if(!a.isPropagationStopped()&&
+f)c.event.trigger(a,b,f,true);else if(!a.isDefaultPrevented()){f=a.target;var i,o=c.nodeName(f,"a")&&e==="click",k=c.event.special[e]||{};if((!k._default||k._default.call(d,a)===false)&&!o&&!(f&&f.nodeName&&c.noData[f.nodeName.toLowerCase()])){try{if(f[e]){if(i=f["on"+e])f["on"+e]=null;c.event.triggered=true;f[e]()}}catch(n){}if(i)f["on"+e]=i;c.event.triggered=false}}},handle:function(a){var b,d,f,e;a=arguments[0]=c.event.fix(a||A.event);a.currentTarget=this;b=a.type.indexOf(".")<0&&!a.exclusive;
+if(!b){d=a.type.split(".");a.type=d.shift();f=new RegExp("(^|\\.)"+d.slice(0).sort().join("\\.(?:.*\\.)?")+"(\\.|$)")}e=c.data(this,"events");d=e[a.type];if(e&&d){d=d.slice(0);e=0;for(var j=d.length;e<j;e++){var i=d[e];if(b||f.test(i.namespace)){a.handler=i.handler;a.data=i.data;a.handleObj=i;i=i.handler.apply(this,arguments);if(i!==w){a.result=i;if(i===false){a.preventDefault();a.stopPropagation()}}if(a.isImmediatePropagationStopped())break}}}return a.result},props:"altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode layerX layerY metaKey newValue offsetX offsetY originalTarget pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target toElement view wheelDelta which".split(" "),
+fix:function(a){if(a[G])return a;var b=a;a=c.Event(b);for(var d=this.props.length,f;d;){f=this.props[--d];a[f]=b[f]}if(!a.target)a.target=a.srcElement||s;if(a.target.nodeType===3)a.target=a.target.parentNode;if(!a.relatedTarget&&a.fromElement)a.relatedTarget=a.fromElement===a.target?a.toElement:a.fromElement;if(a.pageX==null&&a.clientX!=null){b=s.documentElement;d=s.body;a.pageX=a.clientX+(b&&b.scrollLeft||d&&d.scrollLeft||0)-(b&&b.clientLeft||d&&d.clientLeft||0);a.pageY=a.clientY+(b&&b.scrollTop||
+d&&d.scrollTop||0)-(b&&b.clientTop||d&&d.clientTop||0)}if(!a.which&&(a.charCode||a.charCode===0?a.charCode:a.keyCode))a.which=a.charCode||a.keyCode;if(!a.metaKey&&a.ctrlKey)a.metaKey=a.ctrlKey;if(!a.which&&a.button!==w)a.which=a.button&1?1:a.button&2?3:a.button&4?2:0;return a},guid:1E8,proxy:c.proxy,special:{ready:{setup:c.bindReady,teardown:c.noop},live:{add:function(a){c.event.add(this,a.origType,c.extend({},a,{handler:oa}))},remove:function(a){var b=true,d=a.origType.replace(O,"");c.each(c.data(this,
+"events").live||[],function(){if(d===this.origType.replace(O,""))return b=false});b&&c.event.remove(this,a.origType,oa)}},beforeunload:{setup:function(a,b,d){if(this.setInterval)this.onbeforeunload=d;return false},teardown:function(a,b){if(this.onbeforeunload===b)this.onbeforeunload=null}}}};var Ca=s.removeEventListener?function(a,b,d){a.removeEventListener(b,d,false)}:function(a,b,d){a.detachEvent("on"+b,d)};c.Event=function(a){if(!this.preventDefault)return new c.Event(a);if(a&&a.type){this.originalEvent=
+a;this.type=a.type}else this.type=a;this.timeStamp=J();this[G]=true};c.Event.prototype={preventDefault:function(){this.isDefaultPrevented=Z;var a=this.originalEvent;if(a){a.preventDefault&&a.preventDefault();a.returnValue=false}},stopPropagation:function(){this.isPropagationStopped=Z;var a=this.originalEvent;if(a){a.stopPropagation&&a.stopPropagation();a.cancelBubble=true}},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=Z;this.stopPropagation()},isDefaultPrevented:Y,isPropagationStopped:Y,
+isImmediatePropagationStopped:Y};var Da=function(a){var b=a.relatedTarget;try{for(;b&&b!==this;)b=b.parentNode;if(b!==this){a.type=a.data;c.event.handle.apply(this,arguments)}}catch(d){}},Ea=function(a){a.type=a.data;c.event.handle.apply(this,arguments)};c.each({mouseenter:"mouseover",mouseleave:"mouseout"},function(a,b){c.event.special[a]={setup:function(d){c.event.add(this,b,d&&d.selector?Ea:Da,a)},teardown:function(d){c.event.remove(this,b,d&&d.selector?Ea:Da)}}});if(!c.support.submitBubbles)c.event.special.submit=
+{setup:function(){if(this.nodeName.toLowerCase()!=="form"){c.event.add(this,"click.specialSubmit",function(a){var b=a.target,d=b.type;if((d==="submit"||d==="image")&&c(b).closest("form").length)return na("submit",this,arguments)});c.event.add(this,"keypress.specialSubmit",function(a){var b=a.target,d=b.type;if((d==="text"||d==="password")&&c(b).closest("form").length&&a.keyCode===13)return na("submit",this,arguments)})}else return false},teardown:function(){c.event.remove(this,".specialSubmit")}};
+if(!c.support.changeBubbles){var da=/textarea|input|select/i,ea,Fa=function(a){var b=a.type,d=a.value;if(b==="radio"||b==="checkbox")d=a.checked;else if(b==="select-multiple")d=a.selectedIndex>-1?c.map(a.options,function(f){return f.selected}).join("-"):"";else if(a.nodeName.toLowerCase()==="select")d=a.selectedIndex;return d},fa=function(a,b){var d=a.target,f,e;if(!(!da.test(d.nodeName)||d.readOnly)){f=c.data(d,"_change_data");e=Fa(d);if(a.type!=="focusout"||d.type!=="radio")c.data(d,"_change_data",
+e);if(!(f===w||e===f))if(f!=null||e){a.type="change";return c.event.trigger(a,b,d)}}};c.event.special.change={filters:{focusout:fa,click:function(a){var b=a.target,d=b.type;if(d==="radio"||d==="checkbox"||b.nodeName.toLowerCase()==="select")return fa.call(this,a)},keydown:function(a){var b=a.target,d=b.type;if(a.keyCode===13&&b.nodeName.toLowerCase()!=="textarea"||a.keyCode===32&&(d==="checkbox"||d==="radio")||d==="select-multiple")return fa.call(this,a)},beforeactivate:function(a){a=a.target;c.data(a,
+"_change_data",Fa(a))}},setup:function(){if(this.type==="file")return false;for(var a in ea)c.event.add(this,a+".specialChange",ea[a]);return da.test(this.nodeName)},teardown:function(){c.event.remove(this,".specialChange");return da.test(this.nodeName)}};ea=c.event.special.change.filters}s.addEventListener&&c.each({focus:"focusin",blur:"focusout"},function(a,b){function d(f){f=c.event.fix(f);f.type=b;return c.event.handle.call(this,f)}c.event.special[b]={setup:function(){this.addEventListener(a,
+d,true)},teardown:function(){this.removeEventListener(a,d,true)}}});c.each(["bind","one"],function(a,b){c.fn[b]=function(d,f,e){if(typeof d==="object"){for(var j in d)this[b](j,f,d[j],e);return this}if(c.isFunction(f)){e=f;f=w}var i=b==="one"?c.proxy(e,function(k){c(this).unbind(k,i);return e.apply(this,arguments)}):e;if(d==="unload"&&b!=="one")this.one(d,f,e);else{j=0;for(var o=this.length;j<o;j++)c.event.add(this[j],d,i,f)}return this}});c.fn.extend({unbind:function(a,b){if(typeof a==="object"&&
+!a.preventDefault)for(var d in a)this.unbind(d,a[d]);else{d=0;for(var f=this.length;d<f;d++)c.event.remove(this[d],a,b)}return this},delegate:function(a,b,d,f){return this.live(b,d,f,a)},undelegate:function(a,b,d){return arguments.length===0?this.unbind("live"):this.die(b,null,d,a)},trigger:function(a,b){return this.each(function(){c.event.trigger(a,b,this)})},triggerHandler:function(a,b){if(this[0]){a=c.Event(a);a.preventDefault();a.stopPropagation();c.event.trigger(a,b,this[0]);return a.result}},
+toggle:function(a){for(var b=arguments,d=1;d<b.length;)c.proxy(a,b[d++]);return this.click(c.proxy(a,function(f){var e=(c.data(this,"lastToggle"+a.guid)||0)%d;c.data(this,"lastToggle"+a.guid,e+1);f.preventDefault();return b[e].apply(this,arguments)||false}))},hover:function(a,b){return this.mouseenter(a).mouseleave(b||a)}});var Ga={focus:"focusin",blur:"focusout",mouseenter:"mouseover",mouseleave:"mouseout"};c.each(["live","die"],function(a,b){c.fn[b]=function(d,f,e,j){var i,o=0,k,n,r=j||this.selector,
+u=j?this:c(this.context);if(c.isFunction(f)){e=f;f=w}for(d=(d||"").split(" ");(i=d[o++])!=null;){j=O.exec(i);k="";if(j){k=j[0];i=i.replace(O,"")}if(i==="hover")d.push("mouseenter"+k,"mouseleave"+k);else{n=i;if(i==="focus"||i==="blur"){d.push(Ga[i]+k);i+=k}else i=(Ga[i]||i)+k;b==="live"?u.each(function(){c.event.add(this,pa(i,r),{data:f,selector:r,handler:e,origType:i,origHandler:e,preType:n})}):u.unbind(pa(i,r),e)}}return this}});c.each("blur focus focusin focusout load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup error".split(" "),
+function(a,b){c.fn[b]=function(d){return d?this.bind(b,d):this.trigger(b)};if(c.attrFn)c.attrFn[b]=true});A.attachEvent&&!A.addEventListener&&A.attachEvent("onunload",function(){for(var a in c.cache)if(c.cache[a].handle)try{c.event.remove(c.cache[a].handle.elem)}catch(b){}});(function(){function a(g){for(var h="",l,m=0;g[m];m++){l=g[m];if(l.nodeType===3||l.nodeType===4)h+=l.nodeValue;else if(l.nodeType!==8)h+=a(l.childNodes)}return h}function b(g,h,l,m,q,p){q=0;for(var v=m.length;q<v;q++){var t=m[q];
+if(t){t=t[g];for(var y=false;t;){if(t.sizcache===l){y=m[t.sizset];break}if(t.nodeType===1&&!p){t.sizcache=l;t.sizset=q}if(t.nodeName.toLowerCase()===h){y=t;break}t=t[g]}m[q]=y}}}function d(g,h,l,m,q,p){q=0;for(var v=m.length;q<v;q++){var t=m[q];if(t){t=t[g];for(var y=false;t;){if(t.sizcache===l){y=m[t.sizset];break}if(t.nodeType===1){if(!p){t.sizcache=l;t.sizset=q}if(typeof h!=="string"){if(t===h){y=true;break}}else if(k.filter(h,[t]).length>0){y=t;break}}t=t[g]}m[q]=y}}}var f=/((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^[\]]*\]|['"][^'"]*['"]|[^[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,
+e=0,j=Object.prototype.toString,i=false,o=true;[0,0].sort(function(){o=false;return 0});var k=function(g,h,l,m){l=l||[];var q=h=h||s;if(h.nodeType!==1&&h.nodeType!==9)return[];if(!g||typeof g!=="string")return l;for(var p=[],v,t,y,S,H=true,M=x(h),I=g;(f.exec(""),v=f.exec(I))!==null;){I=v[3];p.push(v[1]);if(v[2]){S=v[3];break}}if(p.length>1&&r.exec(g))if(p.length===2&&n.relative[p[0]])t=ga(p[0]+p[1],h);else for(t=n.relative[p[0]]?[h]:k(p.shift(),h);p.length;){g=p.shift();if(n.relative[g])g+=p.shift();
+t=ga(g,t)}else{if(!m&&p.length>1&&h.nodeType===9&&!M&&n.match.ID.test(p[0])&&!n.match.ID.test(p[p.length-1])){v=k.find(p.shift(),h,M);h=v.expr?k.filter(v.expr,v.set)[0]:v.set[0]}if(h){v=m?{expr:p.pop(),set:z(m)}:k.find(p.pop(),p.length===1&&(p[0]==="~"||p[0]==="+")&&h.parentNode?h.parentNode:h,M);t=v.expr?k.filter(v.expr,v.set):v.set;if(p.length>0)y=z(t);else H=false;for(;p.length;){var D=p.pop();v=D;if(n.relative[D])v=p.pop();else D="";if(v==null)v=h;n.relative[D](y,v,M)}}else y=[]}y||(y=t);y||k.error(D||
+g);if(j.call(y)==="[object Array]")if(H)if(h&&h.nodeType===1)for(g=0;y[g]!=null;g++){if(y[g]&&(y[g]===true||y[g].nodeType===1&&E(h,y[g])))l.push(t[g])}else for(g=0;y[g]!=null;g++)y[g]&&y[g].nodeType===1&&l.push(t[g]);else l.push.apply(l,y);else z(y,l);if(S){k(S,q,l,m);k.uniqueSort(l)}return l};k.uniqueSort=function(g){if(B){i=o;g.sort(B);if(i)for(var h=1;h<g.length;h++)g[h]===g[h-1]&&g.splice(h--,1)}return g};k.matches=function(g,h){return k(g,null,null,h)};k.find=function(g,h,l){var m,q;if(!g)return[];
+for(var p=0,v=n.order.length;p<v;p++){var t=n.order[p];if(q=n.leftMatch[t].exec(g)){var y=q[1];q.splice(1,1);if(y.substr(y.length-1)!=="\\"){q[1]=(q[1]||"").replace(/\\/g,"");m=n.find[t](q,h,l);if(m!=null){g=g.replace(n.match[t],"");break}}}}m||(m=h.getElementsByTagName("*"));return{set:m,expr:g}};k.filter=function(g,h,l,m){for(var q=g,p=[],v=h,t,y,S=h&&h[0]&&x(h[0]);g&&h.length;){for(var H in n.filter)if((t=n.leftMatch[H].exec(g))!=null&&t[2]){var M=n.filter[H],I,D;D=t[1];y=false;t.splice(1,1);if(D.substr(D.length-
+1)!=="\\"){if(v===p)p=[];if(n.preFilter[H])if(t=n.preFilter[H](t,v,l,p,m,S)){if(t===true)continue}else y=I=true;if(t)for(var U=0;(D=v[U])!=null;U++)if(D){I=M(D,t,U,v);var Ha=m^!!I;if(l&&I!=null)if(Ha)y=true;else v[U]=false;else if(Ha){p.push(D);y=true}}if(I!==w){l||(v=p);g=g.replace(n.match[H],"");if(!y)return[];break}}}if(g===q)if(y==null)k.error(g);else break;q=g}return v};k.error=function(g){throw"Syntax error, unrecognized expression: "+g;};var n=k.selectors={order:["ID","NAME","TAG"],match:{ID:/#((?:[\w\u00c0-\uFFFF-]|\\.)+)/,
+CLASS:/\.((?:[\w\u00c0-\uFFFF-]|\\.)+)/,NAME:/\[name=['"]*((?:[\w\u00c0-\uFFFF-]|\\.)+)['"]*\]/,ATTR:/\[\s*((?:[\w\u00c0-\uFFFF-]|\\.)+)\s*(?:(\S?=)\s*(['"]*)(.*?)\3|)\s*\]/,TAG:/^((?:[\w\u00c0-\uFFFF\*-]|\\.)+)/,CHILD:/:(only|nth|last|first)-child(?:\((even|odd|[\dn+-]*)\))?/,POS:/:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^-]|$)/,PSEUDO:/:((?:[\w\u00c0-\uFFFF-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/},leftMatch:{},attrMap:{"class":"className","for":"htmlFor"},attrHandle:{href:function(g){return g.getAttribute("href")}},
+relative:{"+":function(g,h){var l=typeof h==="string",m=l&&!/\W/.test(h);l=l&&!m;if(m)h=h.toLowerCase();m=0;for(var q=g.length,p;m<q;m++)if(p=g[m]){for(;(p=p.previousSibling)&&p.nodeType!==1;);g[m]=l||p&&p.nodeName.toLowerCase()===h?p||false:p===h}l&&k.filter(h,g,true)},">":function(g,h){var l=typeof h==="string";if(l&&!/\W/.test(h)){h=h.toLowerCase();for(var m=0,q=g.length;m<q;m++){var p=g[m];if(p){l=p.parentNode;g[m]=l.nodeName.toLowerCase()===h?l:false}}}else{m=0;for(q=g.length;m<q;m++)if(p=g[m])g[m]=
+l?p.parentNode:p.parentNode===h;l&&k.filter(h,g,true)}},"":function(g,h,l){var m=e++,q=d;if(typeof h==="string"&&!/\W/.test(h)){var p=h=h.toLowerCase();q=b}q("parentNode",h,m,g,p,l)},"~":function(g,h,l){var m=e++,q=d;if(typeof h==="string"&&!/\W/.test(h)){var p=h=h.toLowerCase();q=b}q("previousSibling",h,m,g,p,l)}},find:{ID:function(g,h,l){if(typeof h.getElementById!=="undefined"&&!l)return(g=h.getElementById(g[1]))?[g]:[]},NAME:function(g,h){if(typeof h.getElementsByName!=="undefined"){var l=[];
+h=h.getElementsByName(g[1]);for(var m=0,q=h.length;m<q;m++)h[m].getAttribute("name")===g[1]&&l.push(h[m]);return l.length===0?null:l}},TAG:function(g,h){return h.getElementsByTagName(g[1])}},preFilter:{CLASS:function(g,h,l,m,q,p){g=" "+g[1].replace(/\\/g,"")+" ";if(p)return g;p=0;for(var v;(v=h[p])!=null;p++)if(v)if(q^(v.className&&(" "+v.className+" ").replace(/[\t\n]/g," ").indexOf(g)>=0))l||m.push(v);else if(l)h[p]=false;return false},ID:function(g){return g[1].replace(/\\/g,"")},TAG:function(g){return g[1].toLowerCase()},
+CHILD:function(g){if(g[1]==="nth"){var h=/(-?)(\d*)n((?:\+|-)?\d*)/.exec(g[2]==="even"&&"2n"||g[2]==="odd"&&"2n+1"||!/\D/.test(g[2])&&"0n+"+g[2]||g[2]);g[2]=h[1]+(h[2]||1)-0;g[3]=h[3]-0}g[0]=e++;return g},ATTR:function(g,h,l,m,q,p){h=g[1].replace(/\\/g,"");if(!p&&n.attrMap[h])g[1]=n.attrMap[h];if(g[2]==="~=")g[4]=" "+g[4]+" ";return g},PSEUDO:function(g,h,l,m,q){if(g[1]==="not")if((f.exec(g[3])||"").length>1||/^\w/.test(g[3]))g[3]=k(g[3],null,null,h);else{g=k.filter(g[3],h,l,true^q);l||m.push.apply(m,
+g);return false}else if(n.match.POS.test(g[0])||n.match.CHILD.test(g[0]))return true;return g},POS:function(g){g.unshift(true);return g}},filters:{enabled:function(g){return g.disabled===false&&g.type!=="hidden"},disabled:function(g){return g.disabled===true},checked:function(g){return g.checked===true},selected:function(g){return g.selected===true},parent:function(g){return!!g.firstChild},empty:function(g){return!g.firstChild},has:function(g,h,l){return!!k(l[3],g).length},header:function(g){return/h\d/i.test(g.nodeName)},
+text:function(g){return"text"===g.type},radio:function(g){return"radio"===g.type},checkbox:function(g){return"checkbox"===g.type},file:function(g){return"file"===g.type},password:function(g){return"password"===g.type},submit:function(g){return"submit"===g.type},image:function(g){return"image"===g.type},reset:function(g){return"reset"===g.type},button:function(g){return"button"===g.type||g.nodeName.toLowerCase()==="button"},input:function(g){return/input|select|textarea|button/i.test(g.nodeName)}},
+setFilters:{first:function(g,h){return h===0},last:function(g,h,l,m){return h===m.length-1},even:function(g,h){return h%2===0},odd:function(g,h){return h%2===1},lt:function(g,h,l){return h<l[3]-0},gt:function(g,h,l){return h>l[3]-0},nth:function(g,h,l){return l[3]-0===h},eq:function(g,h,l){return l[3]-0===h}},filter:{PSEUDO:function(g,h,l,m){var q=h[1],p=n.filters[q];if(p)return p(g,l,h,m);else if(q==="contains")return(g.textContent||g.innerText||a([g])||"").indexOf(h[3])>=0;else if(q==="not"){h=
+h[3];l=0;for(m=h.length;l<m;l++)if(h[l]===g)return false;return true}else k.error("Syntax error, unrecognized expression: "+q)},CHILD:function(g,h){var l=h[1],m=g;switch(l){case "only":case "first":for(;m=m.previousSibling;)if(m.nodeType===1)return false;if(l==="first")return true;m=g;case "last":for(;m=m.nextSibling;)if(m.nodeType===1)return false;return true;case "nth":l=h[2];var q=h[3];if(l===1&&q===0)return true;h=h[0];var p=g.parentNode;if(p&&(p.sizcache!==h||!g.nodeIndex)){var v=0;for(m=p.firstChild;m;m=
+m.nextSibling)if(m.nodeType===1)m.nodeIndex=++v;p.sizcache=h}g=g.nodeIndex-q;return l===0?g===0:g%l===0&&g/l>=0}},ID:function(g,h){return g.nodeType===1&&g.getAttribute("id")===h},TAG:function(g,h){return h==="*"&&g.nodeType===1||g.nodeName.toLowerCase()===h},CLASS:function(g,h){return(" "+(g.className||g.getAttribute("class"))+" ").indexOf(h)>-1},ATTR:function(g,h){var l=h[1];g=n.attrHandle[l]?n.attrHandle[l](g):g[l]!=null?g[l]:g.getAttribute(l);l=g+"";var m=h[2];h=h[4];return g==null?m==="!=":m===
+"="?l===h:m==="*="?l.indexOf(h)>=0:m==="~="?(" "+l+" ").indexOf(h)>=0:!h?l&&g!==false:m==="!="?l!==h:m==="^="?l.indexOf(h)===0:m==="$="?l.substr(l.length-h.length)===h:m==="|="?l===h||l.substr(0,h.length+1)===h+"-":false},POS:function(g,h,l,m){var q=n.setFilters[h[2]];if(q)return q(g,l,h,m)}}},r=n.match.POS;for(var u in n.match){n.match[u]=new RegExp(n.match[u].source+/(?![^\[]*\])(?![^\(]*\))/.source);n.leftMatch[u]=new RegExp(/(^(?:.|\r|\n)*?)/.source+n.match[u].source.replace(/\\(\d+)/g,function(g,
+h){return"\\"+(h-0+1)}))}var z=function(g,h){g=Array.prototype.slice.call(g,0);if(h){h.push.apply(h,g);return h}return g};try{Array.prototype.slice.call(s.documentElement.childNodes,0)}catch(C){z=function(g,h){h=h||[];if(j.call(g)==="[object Array]")Array.prototype.push.apply(h,g);else if(typeof g.length==="number")for(var l=0,m=g.length;l<m;l++)h.push(g[l]);else for(l=0;g[l];l++)h.push(g[l]);return h}}var B;if(s.documentElement.compareDocumentPosition)B=function(g,h){if(!g.compareDocumentPosition||
+!h.compareDocumentPosition){if(g==h)i=true;return g.compareDocumentPosition?-1:1}g=g.compareDocumentPosition(h)&4?-1:g===h?0:1;if(g===0)i=true;return g};else if("sourceIndex"in s.documentElement)B=function(g,h){if(!g.sourceIndex||!h.sourceIndex){if(g==h)i=true;return g.sourceIndex?-1:1}g=g.sourceIndex-h.sourceIndex;if(g===0)i=true;return g};else if(s.createRange)B=function(g,h){if(!g.ownerDocument||!h.ownerDocument){if(g==h)i=true;return g.ownerDocument?-1:1}var l=g.ownerDocument.createRange(),m=
+h.ownerDocument.createRange();l.setStart(g,0);l.setEnd(g,0);m.setStart(h,0);m.setEnd(h,0);g=l.compareBoundaryPoints(Range.START_TO_END,m);if(g===0)i=true;return g};(function(){var g=s.createElement("div"),h="script"+(new Date).getTime();g.innerHTML="<a name='"+h+"'/>";var l=s.documentElement;l.insertBefore(g,l.firstChild);if(s.getElementById(h)){n.find.ID=function(m,q,p){if(typeof q.getElementById!=="undefined"&&!p)return(q=q.getElementById(m[1]))?q.id===m[1]||typeof q.getAttributeNode!=="undefined"&&
+q.getAttributeNode("id").nodeValue===m[1]?[q]:w:[]};n.filter.ID=function(m,q){var p=typeof m.getAttributeNode!=="undefined"&&m.getAttributeNode("id");return m.nodeType===1&&p&&p.nodeValue===q}}l.removeChild(g);l=g=null})();(function(){var g=s.createElement("div");g.appendChild(s.createComment(""));if(g.getElementsByTagName("*").length>0)n.find.TAG=function(h,l){l=l.getElementsByTagName(h[1]);if(h[1]==="*"){h=[];for(var m=0;l[m];m++)l[m].nodeType===1&&h.push(l[m]);l=h}return l};g.innerHTML="<a href='#'></a>";
+if(g.firstChild&&typeof g.firstChild.getAttribute!=="undefined"&&g.firstChild.getAttribute("href")!=="#")n.attrHandle.href=function(h){return h.getAttribute("href",2)};g=null})();s.querySelectorAll&&function(){var g=k,h=s.createElement("div");h.innerHTML="<p class='TEST'></p>";if(!(h.querySelectorAll&&h.querySelectorAll(".TEST").length===0)){k=function(m,q,p,v){q=q||s;if(!v&&q.nodeType===9&&!x(q))try{return z(q.querySelectorAll(m),p)}catch(t){}return g(m,q,p,v)};for(var l in g)k[l]=g[l];h=null}}();
+(function(){var g=s.createElement("div");g.innerHTML="<div class='test e'></div><div class='test'></div>";if(!(!g.getElementsByClassName||g.getElementsByClassName("e").length===0)){g.lastChild.className="e";if(g.getElementsByClassName("e").length!==1){n.order.splice(1,0,"CLASS");n.find.CLASS=function(h,l,m){if(typeof l.getElementsByClassName!=="undefined"&&!m)return l.getElementsByClassName(h[1])};g=null}}})();var E=s.compareDocumentPosition?function(g,h){return!!(g.compareDocumentPosition(h)&16)}:
+function(g,h){return g!==h&&(g.contains?g.contains(h):true)},x=function(g){return(g=(g?g.ownerDocument||g:0).documentElement)?g.nodeName!=="HTML":false},ga=function(g,h){var l=[],m="",q;for(h=h.nodeType?[h]:h;q=n.match.PSEUDO.exec(g);){m+=q[0];g=g.replace(n.match.PSEUDO,"")}g=n.relative[g]?g+"*":g;q=0;for(var p=h.length;q<p;q++)k(g,h[q],l);return k.filter(m,l)};c.find=k;c.expr=k.selectors;c.expr[":"]=c.expr.filters;c.unique=k.uniqueSort;c.text=a;c.isXMLDoc=x;c.contains=E})();var eb=/Until$/,fb=/^(?:parents|prevUntil|prevAll)/,
+gb=/,/;R=Array.prototype.slice;var Ia=function(a,b,d){if(c.isFunction(b))return c.grep(a,function(e,j){return!!b.call(e,j,e)===d});else if(b.nodeType)return c.grep(a,function(e){return e===b===d});else if(typeof b==="string"){var f=c.grep(a,function(e){return e.nodeType===1});if(Ua.test(b))return c.filter(b,f,!d);else b=c.filter(b,f)}return c.grep(a,function(e){return c.inArray(e,b)>=0===d})};c.fn.extend({find:function(a){for(var b=this.pushStack("","find",a),d=0,f=0,e=this.length;f<e;f++){d=b.length;
+c.find(a,this[f],b);if(f>0)for(var j=d;j<b.length;j++)for(var i=0;i<d;i++)if(b[i]===b[j]){b.splice(j--,1);break}}return b},has:function(a){var b=c(a);return this.filter(function(){for(var d=0,f=b.length;d<f;d++)if(c.contains(this,b[d]))return true})},not:function(a){return this.pushStack(Ia(this,a,false),"not",a)},filter:function(a){return this.pushStack(Ia(this,a,true),"filter",a)},is:function(a){return!!a&&c.filter(a,this).length>0},closest:function(a,b){if(c.isArray(a)){var d=[],f=this[0],e,j=
+{},i;if(f&&a.length){e=0;for(var o=a.length;e<o;e++){i=a[e];j[i]||(j[i]=c.expr.match.POS.test(i)?c(i,b||this.context):i)}for(;f&&f.ownerDocument&&f!==b;){for(i in j){e=j[i];if(e.jquery?e.index(f)>-1:c(f).is(e)){d.push({selector:i,elem:f});delete j[i]}}f=f.parentNode}}return d}var k=c.expr.match.POS.test(a)?c(a,b||this.context):null;return this.map(function(n,r){for(;r&&r.ownerDocument&&r!==b;){if(k?k.index(r)>-1:c(r).is(a))return r;r=r.parentNode}return null})},index:function(a){if(!a||typeof a===
+"string")return c.inArray(this[0],a?c(a):this.parent().children());return c.inArray(a.jquery?a[0]:a,this)},add:function(a,b){a=typeof a==="string"?c(a,b||this.context):c.makeArray(a);b=c.merge(this.get(),a);return this.pushStack(qa(a[0])||qa(b[0])?b:c.unique(b))},andSelf:function(){return this.add(this.prevObject)}});c.each({parent:function(a){return(a=a.parentNode)&&a.nodeType!==11?a:null},parents:function(a){return c.dir(a,"parentNode")},parentsUntil:function(a,b,d){return c.dir(a,"parentNode",
+d)},next:function(a){return c.nth(a,2,"nextSibling")},prev:function(a){return c.nth(a,2,"previousSibling")},nextAll:function(a){return c.dir(a,"nextSibling")},prevAll:function(a){return c.dir(a,"previousSibling")},nextUntil:function(a,b,d){return c.dir(a,"nextSibling",d)},prevUntil:function(a,b,d){return c.dir(a,"previousSibling",d)},siblings:function(a){return c.sibling(a.parentNode.firstChild,a)},children:function(a){return c.sibling(a.firstChild)},contents:function(a){return c.nodeName(a,"iframe")?
+a.contentDocument||a.contentWindow.document:c.makeArray(a.childNodes)}},function(a,b){c.fn[a]=function(d,f){var e=c.map(this,b,d);eb.test(a)||(f=d);if(f&&typeof f==="string")e=c.filter(f,e);e=this.length>1?c.unique(e):e;if((this.length>1||gb.test(f))&&fb.test(a))e=e.reverse();return this.pushStack(e,a,R.call(arguments).join(","))}});c.extend({filter:function(a,b,d){if(d)a=":not("+a+")";return c.find.matches(a,b)},dir:function(a,b,d){var f=[];for(a=a[b];a&&a.nodeType!==9&&(d===w||a.nodeType!==1||!c(a).is(d));){a.nodeType===
+1&&f.push(a);a=a[b]}return f},nth:function(a,b,d){b=b||1;for(var f=0;a;a=a[d])if(a.nodeType===1&&++f===b)break;return a},sibling:function(a,b){for(var d=[];a;a=a.nextSibling)a.nodeType===1&&a!==b&&d.push(a);return d}});var Ja=/ jQuery\d+="(?:\d+|null)"/g,V=/^\s+/,Ka=/(<([\w:]+)[^>]*?)\/>/g,hb=/^(?:area|br|col|embed|hr|img|input|link|meta|param)$/i,La=/<([\w:]+)/,ib=/<tbody/i,jb=/<|&#?\w+;/,ta=/<script|<object|<embed|<option|<style/i,ua=/checked\s*(?:[^=]|=\s*.checked.)/i,Ma=function(a,b,d){return hb.test(d)?
+a:b+"></"+d+">"},F={option:[1,"<select multiple='multiple'>","</select>"],legend:[1,"<fieldset>","</fieldset>"],thead:[1,"<table>","</table>"],tr:[2,"<table><tbody>","</tbody></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],col:[2,"<table><tbody></tbody><colgroup>","</colgroup></table>"],area:[1,"<map>","</map>"],_default:[0,"",""]};F.optgroup=F.option;F.tbody=F.tfoot=F.colgroup=F.caption=F.thead;F.th=F.td;if(!c.support.htmlSerialize)F._default=[1,"div<div>","</div>"];c.fn.extend({text:function(a){if(c.isFunction(a))return this.each(function(b){var d=
+c(this);d.text(a.call(this,b,d.text()))});if(typeof a!=="object"&&a!==w)return this.empty().append((this[0]&&this[0].ownerDocument||s).createTextNode(a));return c.text(this)},wrapAll:function(a){if(c.isFunction(a))return this.each(function(d){c(this).wrapAll(a.call(this,d))});if(this[0]){var b=c(a,this[0].ownerDocument).eq(0).clone(true);this[0].parentNode&&b.insertBefore(this[0]);b.map(function(){for(var d=this;d.firstChild&&d.firstChild.nodeType===1;)d=d.firstChild;return d}).append(this)}return this},
+wrapInner:function(a){if(c.isFunction(a))return this.each(function(b){c(this).wrapInner(a.call(this,b))});return this.each(function(){var b=c(this),d=b.contents();d.length?d.wrapAll(a):b.append(a)})},wrap:function(a){return this.each(function(){c(this).wrapAll(a)})},unwrap:function(){return this.parent().each(function(){c.nodeName(this,"body")||c(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,true,function(a){this.nodeType===1&&this.appendChild(a)})},
+prepend:function(){return this.domManip(arguments,true,function(a){this.nodeType===1&&this.insertBefore(a,this.firstChild)})},before:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,false,function(b){this.parentNode.insertBefore(b,this)});else if(arguments.length){var a=c(arguments[0]);a.push.apply(a,this.toArray());return this.pushStack(a,"before",arguments)}},after:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,false,function(b){this.parentNode.insertBefore(b,
+this.nextSibling)});else if(arguments.length){var a=this.pushStack(this,"after",arguments);a.push.apply(a,c(arguments[0]).toArray());return a}},remove:function(a,b){for(var d=0,f;(f=this[d])!=null;d++)if(!a||c.filter(a,[f]).length){if(!b&&f.nodeType===1){c.cleanData(f.getElementsByTagName("*"));c.cleanData([f])}f.parentNode&&f.parentNode.removeChild(f)}return this},empty:function(){for(var a=0,b;(b=this[a])!=null;a++)for(b.nodeType===1&&c.cleanData(b.getElementsByTagName("*"));b.firstChild;)b.removeChild(b.firstChild);
+return this},clone:function(a){var b=this.map(function(){if(!c.support.noCloneEvent&&!c.isXMLDoc(this)){var d=this.outerHTML,f=this.ownerDocument;if(!d){d=f.createElement("div");d.appendChild(this.cloneNode(true));d=d.innerHTML}return c.clean([d.replace(Ja,"").replace(/=([^="'>\s]+\/)>/g,'="$1">').replace(V,"")],f)[0]}else return this.cloneNode(true)});if(a===true){ra(this,b);ra(this.find("*"),b.find("*"))}return b},html:function(a){if(a===w)return this[0]&&this[0].nodeType===1?this[0].innerHTML.replace(Ja,
+""):null;else if(typeof a==="string"&&!ta.test(a)&&(c.support.leadingWhitespace||!V.test(a))&&!F[(La.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(Ka,Ma);try{for(var b=0,d=this.length;b<d;b++)if(this[b].nodeType===1){c.cleanData(this[b].getElementsByTagName("*"));this[b].innerHTML=a}}catch(f){this.empty().append(a)}}else c.isFunction(a)?this.each(function(e){var j=c(this),i=j.html();j.empty().append(function(){return a.call(this,e,i)})}):this.empty().append(a);return this},replaceWith:function(a){if(this[0]&&
+this[0].parentNode){if(c.isFunction(a))return this.each(function(b){var d=c(this),f=d.html();d.replaceWith(a.call(this,b,f))});if(typeof a!=="string")a=c(a).detach();return this.each(function(){var b=this.nextSibling,d=this.parentNode;c(this).remove();b?c(b).before(a):c(d).append(a)})}else return this.pushStack(c(c.isFunction(a)?a():a),"replaceWith",a)},detach:function(a){return this.remove(a,true)},domManip:function(a,b,d){function f(u){return c.nodeName(u,"table")?u.getElementsByTagName("tbody")[0]||
+u.appendChild(u.ownerDocument.createElement("tbody")):u}var e,j,i=a[0],o=[],k;if(!c.support.checkClone&&arguments.length===3&&typeof i==="string"&&ua.test(i))return this.each(function(){c(this).domManip(a,b,d,true)});if(c.isFunction(i))return this.each(function(u){var z=c(this);a[0]=i.call(this,u,b?z.html():w);z.domManip(a,b,d)});if(this[0]){e=i&&i.parentNode;e=c.support.parentNode&&e&&e.nodeType===11&&e.childNodes.length===this.length?{fragment:e}:sa(a,this,o);k=e.fragment;if(j=k.childNodes.length===
+1?(k=k.firstChild):k.firstChild){b=b&&c.nodeName(j,"tr");for(var n=0,r=this.length;n<r;n++)d.call(b?f(this[n],j):this[n],n>0||e.cacheable||this.length>1?k.cloneNode(true):k)}o.length&&c.each(o,Qa)}return this}});c.fragments={};c.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){c.fn[a]=function(d){var f=[];d=c(d);var e=this.length===1&&this[0].parentNode;if(e&&e.nodeType===11&&e.childNodes.length===1&&d.length===1){d[b](this[0]);
+return this}else{e=0;for(var j=d.length;e<j;e++){var i=(e>0?this.clone(true):this).get();c.fn[b].apply(c(d[e]),i);f=f.concat(i)}return this.pushStack(f,a,d.selector)}}});c.extend({clean:function(a,b,d,f){b=b||s;if(typeof b.createElement==="undefined")b=b.ownerDocument||b[0]&&b[0].ownerDocument||s;for(var e=[],j=0,i;(i=a[j])!=null;j++){if(typeof i==="number")i+="";if(i){if(typeof i==="string"&&!jb.test(i))i=b.createTextNode(i);else if(typeof i==="string"){i=i.replace(Ka,Ma);var o=(La.exec(i)||["",
+""])[1].toLowerCase(),k=F[o]||F._default,n=k[0],r=b.createElement("div");for(r.innerHTML=k[1]+i+k[2];n--;)r=r.lastChild;if(!c.support.tbody){n=ib.test(i);o=o==="table"&&!n?r.firstChild&&r.firstChild.childNodes:k[1]==="<table>"&&!n?r.childNodes:[];for(k=o.length-1;k>=0;--k)c.nodeName(o[k],"tbody")&&!o[k].childNodes.length&&o[k].parentNode.removeChild(o[k])}!c.support.leadingWhitespace&&V.test(i)&&r.insertBefore(b.createTextNode(V.exec(i)[0]),r.firstChild);i=r.childNodes}if(i.nodeType)e.push(i);else e=
+c.merge(e,i)}}if(d)for(j=0;e[j];j++)if(f&&c.nodeName(e[j],"script")&&(!e[j].type||e[j].type.toLowerCase()==="text/javascript"))f.push(e[j].parentNode?e[j].parentNode.removeChild(e[j]):e[j]);else{e[j].nodeType===1&&e.splice.apply(e,[j+1,0].concat(c.makeArray(e[j].getElementsByTagName("script"))));d.appendChild(e[j])}return e},cleanData:function(a){for(var b,d,f=c.cache,e=c.event.special,j=c.support.deleteExpando,i=0,o;(o=a[i])!=null;i++)if(d=o[c.expando]){b=f[d];if(b.events)for(var k in b.events)e[k]?
+c.event.remove(o,k):Ca(o,k,b.handle);if(j)delete o[c.expando];else o.removeAttribute&&o.removeAttribute(c.expando);delete f[d]}}});var kb=/z-?index|font-?weight|opacity|zoom|line-?height/i,Na=/alpha\([^)]*\)/,Oa=/opacity=([^)]*)/,ha=/float/i,ia=/-([a-z])/ig,lb=/([A-Z])/g,mb=/^-?\d+(?:px)?$/i,nb=/^-?\d/,ob={position:"absolute",visibility:"hidden",display:"block"},pb=["Left","Right"],qb=["Top","Bottom"],rb=s.defaultView&&s.defaultView.getComputedStyle,Pa=c.support.cssFloat?"cssFloat":"styleFloat",ja=
+function(a,b){return b.toUpperCase()};c.fn.css=function(a,b){return X(this,a,b,true,function(d,f,e){if(e===w)return c.curCSS(d,f);if(typeof e==="number"&&!kb.test(f))e+="px";c.style(d,f,e)})};c.extend({style:function(a,b,d){if(!a||a.nodeType===3||a.nodeType===8)return w;if((b==="width"||b==="height")&&parseFloat(d)<0)d=w;var f=a.style||a,e=d!==w;if(!c.support.opacity&&b==="opacity"){if(e){f.zoom=1;b=parseInt(d,10)+""==="NaN"?"":"alpha(opacity="+d*100+")";a=f.filter||c.curCSS(a,"filter")||"";f.filter=
+Na.test(a)?a.replace(Na,b):b}return f.filter&&f.filter.indexOf("opacity=")>=0?parseFloat(Oa.exec(f.filter)[1])/100+"":""}if(ha.test(b))b=Pa;b=b.replace(ia,ja);if(e)f[b]=d;return f[b]},css:function(a,b,d,f){if(b==="width"||b==="height"){var e,j=b==="width"?pb:qb;function i(){e=b==="width"?a.offsetWidth:a.offsetHeight;f!=="border"&&c.each(j,function(){f||(e-=parseFloat(c.curCSS(a,"padding"+this,true))||0);if(f==="margin")e+=parseFloat(c.curCSS(a,"margin"+this,true))||0;else e-=parseFloat(c.curCSS(a,
+"border"+this+"Width",true))||0})}a.offsetWidth!==0?i():c.swap(a,ob,i);return Math.max(0,Math.round(e))}return c.curCSS(a,b,d)},curCSS:function(a,b,d){var f,e=a.style;if(!c.support.opacity&&b==="opacity"&&a.currentStyle){f=Oa.test(a.currentStyle.filter||"")?parseFloat(RegExp.$1)/100+"":"";return f===""?"1":f}if(ha.test(b))b=Pa;if(!d&&e&&e[b])f=e[b];else if(rb){if(ha.test(b))b="float";b=b.replace(lb,"-$1").toLowerCase();e=a.ownerDocument.defaultView;if(!e)return null;if(a=e.getComputedStyle(a,null))f=
+a.getPropertyValue(b);if(b==="opacity"&&f==="")f="1"}else if(a.currentStyle){d=b.replace(ia,ja);f=a.currentStyle[b]||a.currentStyle[d];if(!mb.test(f)&&nb.test(f)){b=e.left;var j=a.runtimeStyle.left;a.runtimeStyle.left=a.currentStyle.left;e.left=d==="fontSize"?"1em":f||0;f=e.pixelLeft+"px";e.left=b;a.runtimeStyle.left=j}}return f},swap:function(a,b,d){var f={};for(var e in b){f[e]=a.style[e];a.style[e]=b[e]}d.call(a);for(e in b)a.style[e]=f[e]}});if(c.expr&&c.expr.filters){c.expr.filters.hidden=function(a){var b=
+a.offsetWidth,d=a.offsetHeight,f=a.nodeName.toLowerCase()==="tr";return b===0&&d===0&&!f?true:b>0&&d>0&&!f?false:c.curCSS(a,"display")==="none"};c.expr.filters.visible=function(a){return!c.expr.filters.hidden(a)}}var sb=J(),tb=/<script(.|\s)*?\/script>/gi,ub=/select|textarea/i,vb=/color|date|datetime|email|hidden|month|number|password|range|search|tel|text|time|url|week/i,N=/=\?(&|$)/,ka=/\?/,wb=/(\?|&)_=.*?(&|$)/,xb=/^(\w+:)?\/\/([^\/?#]+)/,yb=/%20/g,zb=c.fn.load;c.fn.extend({load:function(a,b,d){if(typeof a!==
+"string")return zb.call(this,a);else if(!this.length)return this;var f=a.indexOf(" ");if(f>=0){var e=a.slice(f,a.length);a=a.slice(0,f)}f="GET";if(b)if(c.isFunction(b)){d=b;b=null}else if(typeof b==="object"){b=c.param(b,c.ajaxSettings.traditional);f="POST"}var j=this;c.ajax({url:a,type:f,dataType:"html",data:b,complete:function(i,o){if(o==="success"||o==="notmodified")j.html(e?c("<div />").append(i.responseText.replace(tb,"")).find(e):i.responseText);d&&j.each(d,[i.responseText,o,i])}});return this},
+serialize:function(){return c.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?c.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||ub.test(this.nodeName)||vb.test(this.type))}).map(function(a,b){a=c(this).val();return a==null?null:c.isArray(a)?c.map(a,function(d){return{name:b.name,value:d}}):{name:b.name,value:a}}).get()}});c.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "),
+function(a,b){c.fn[b]=function(d){return this.bind(b,d)}});c.extend({get:function(a,b,d,f){if(c.isFunction(b)){f=f||d;d=b;b=null}return c.ajax({type:"GET",url:a,data:b,success:d,dataType:f})},getScript:function(a,b){return c.get(a,null,b,"script")},getJSON:function(a,b,d){return c.get(a,b,d,"json")},post:function(a,b,d,f){if(c.isFunction(b)){f=f||d;d=b;b={}}return c.ajax({type:"POST",url:a,data:b,success:d,dataType:f})},ajaxSetup:function(a){c.extend(c.ajaxSettings,a)},ajaxSettings:{url:location.href,
+global:true,type:"GET",contentType:"application/x-www-form-urlencoded",processData:true,async:true,xhr:A.XMLHttpRequest&&(A.location.protocol!=="file:"||!A.ActiveXObject)?function(){return new A.XMLHttpRequest}:function(){try{return new A.ActiveXObject("Microsoft.XMLHTTP")}catch(a){}},accepts:{xml:"application/xml, text/xml",html:"text/html",script:"text/javascript, application/javascript",json:"application/json, text/javascript",text:"text/plain",_default:"*/*"}},lastModified:{},etag:{},ajax:function(a){function b(){e.success&&
+e.success.call(k,o,i,x);e.global&&f("ajaxSuccess",[x,e])}function d(){e.complete&&e.complete.call(k,x,i);e.global&&f("ajaxComplete",[x,e]);e.global&&!--c.active&&c.event.trigger("ajaxStop")}function f(q,p){(e.context?c(e.context):c.event).trigger(q,p)}var e=c.extend(true,{},c.ajaxSettings,a),j,i,o,k=a&&a.context||e,n=e.type.toUpperCase();if(e.data&&e.processData&&typeof e.data!=="string")e.data=c.param(e.data,e.traditional);if(e.dataType==="jsonp"){if(n==="GET")N.test(e.url)||(e.url+=(ka.test(e.url)?
+"&":"?")+(e.jsonp||"callback")+"=?");else if(!e.data||!N.test(e.data))e.data=(e.data?e.data+"&":"")+(e.jsonp||"callback")+"=?";e.dataType="json"}if(e.dataType==="json"&&(e.data&&N.test(e.data)||N.test(e.url))){j=e.jsonpCallback||"jsonp"+sb++;if(e.data)e.data=(e.data+"").replace(N,"="+j+"$1");e.url=e.url.replace(N,"="+j+"$1");e.dataType="script";A[j]=A[j]||function(q){o=q;b();d();A[j]=w;try{delete A[j]}catch(p){}z&&z.removeChild(C)}}if(e.dataType==="script"&&e.cache===null)e.cache=false;if(e.cache===
+false&&n==="GET"){var r=J(),u=e.url.replace(wb,"$1_="+r+"$2");e.url=u+(u===e.url?(ka.test(e.url)?"&":"?")+"_="+r:"")}if(e.data&&n==="GET")e.url+=(ka.test(e.url)?"&":"?")+e.data;e.global&&!c.active++&&c.event.trigger("ajaxStart");r=(r=xb.exec(e.url))&&(r[1]&&r[1]!==location.protocol||r[2]!==location.host);if(e.dataType==="script"&&n==="GET"&&r){var z=s.getElementsByTagName("head")[0]||s.documentElement,C=s.createElement("script");C.src=e.url;if(e.scriptCharset)C.charset=e.scriptCharset;if(!j){var B=
+false;C.onload=C.onreadystatechange=function(){if(!B&&(!this.readyState||this.readyState==="loaded"||this.readyState==="complete")){B=true;b();d();C.onload=C.onreadystatechange=null;z&&C.parentNode&&z.removeChild(C)}}}z.insertBefore(C,z.firstChild);return w}var E=false,x=e.xhr();if(x){e.username?x.open(n,e.url,e.async,e.username,e.password):x.open(n,e.url,e.async);try{if(e.data||a&&a.contentType)x.setRequestHeader("Content-Type",e.contentType);if(e.ifModified){c.lastModified[e.url]&&x.setRequestHeader("If-Modified-Since",
+c.lastModified[e.url]);c.etag[e.url]&&x.setRequestHeader("If-None-Match",c.etag[e.url])}r||x.setRequestHeader("X-Requested-With","XMLHttpRequest");x.setRequestHeader("Accept",e.dataType&&e.accepts[e.dataType]?e.accepts[e.dataType]+", */*":e.accepts._default)}catch(ga){}if(e.beforeSend&&e.beforeSend.call(k,x,e)===false){e.global&&!--c.active&&c.event.trigger("ajaxStop");x.abort();return false}e.global&&f("ajaxSend",[x,e]);var g=x.onreadystatechange=function(q){if(!x||x.readyState===0||q==="abort"){E||
+d();E=true;if(x)x.onreadystatechange=c.noop}else if(!E&&x&&(x.readyState===4||q==="timeout")){E=true;x.onreadystatechange=c.noop;i=q==="timeout"?"timeout":!c.httpSuccess(x)?"error":e.ifModified&&c.httpNotModified(x,e.url)?"notmodified":"success";var p;if(i==="success")try{o=c.httpData(x,e.dataType,e)}catch(v){i="parsererror";p=v}if(i==="success"||i==="notmodified")j||b();else c.handleError(e,x,i,p);d();q==="timeout"&&x.abort();if(e.async)x=null}};try{var h=x.abort;x.abort=function(){x&&h.call(x);
+g("abort")}}catch(l){}e.async&&e.timeout>0&&setTimeout(function(){x&&!E&&g("timeout")},e.timeout);try{x.send(n==="POST"||n==="PUT"||n==="DELETE"?e.data:null)}catch(m){c.handleError(e,x,null,m);d()}e.async||g();return x}},handleError:function(a,b,d,f){if(a.error)a.error.call(a.context||a,b,d,f);if(a.global)(a.context?c(a.context):c.event).trigger("ajaxError",[b,a,f])},active:0,httpSuccess:function(a){try{return!a.status&&location.protocol==="file:"||a.status>=200&&a.status<300||a.status===304||a.status===
+1223||a.status===0}catch(b){}return false},httpNotModified:function(a,b){var d=a.getResponseHeader("Last-Modified"),f=a.getResponseHeader("Etag");if(d)c.lastModified[b]=d;if(f)c.etag[b]=f;return a.status===304||a.status===0},httpData:function(a,b,d){var f=a.getResponseHeader("content-type")||"",e=b==="xml"||!b&&f.indexOf("xml")>=0;a=e?a.responseXML:a.responseText;e&&a.documentElement.nodeName==="parsererror"&&c.error("parsererror");if(d&&d.dataFilter)a=d.dataFilter(a,b);if(typeof a==="string")if(b===
+"json"||!b&&f.indexOf("json")>=0)a=c.parseJSON(a);else if(b==="script"||!b&&f.indexOf("javascript")>=0)c.globalEval(a);return a},param:function(a,b){function d(i,o){if(c.isArray(o))c.each(o,function(k,n){b||/\[\]$/.test(i)?f(i,n):d(i+"["+(typeof n==="object"||c.isArray(n)?k:"")+"]",n)});else!b&&o!=null&&typeof o==="object"?c.each(o,function(k,n){d(i+"["+k+"]",n)}):f(i,o)}function f(i,o){o=c.isFunction(o)?o():o;e[e.length]=encodeURIComponent(i)+"="+encodeURIComponent(o)}var e=[];if(b===w)b=c.ajaxSettings.traditional;
+if(c.isArray(a)||a.jquery)c.each(a,function(){f(this.name,this.value)});else for(var j in a)d(j,a[j]);return e.join("&").replace(yb,"+")}});var la={},Ab=/toggle|show|hide/,Bb=/^([+-]=)?([\d+-.]+)(.*)$/,W,va=[["height","marginTop","marginBottom","paddingTop","paddingBottom"],["width","marginLeft","marginRight","paddingLeft","paddingRight"],["opacity"]];c.fn.extend({show:function(a,b){if(a||a===0)return this.animate(K("show",3),a,b);else{a=0;for(b=this.length;a<b;a++){var d=c.data(this[a],"olddisplay");
+this[a].style.display=d||"";if(c.css(this[a],"display")==="none"){d=this[a].nodeName;var f;if(la[d])f=la[d];else{var e=c("<"+d+" />").appendTo("body");f=e.css("display");if(f==="none")f="block";e.remove();la[d]=f}c.data(this[a],"olddisplay",f)}}a=0;for(b=this.length;a<b;a++)this[a].style.display=c.data(this[a],"olddisplay")||"";return this}},hide:function(a,b){if(a||a===0)return this.animate(K("hide",3),a,b);else{a=0;for(b=this.length;a<b;a++){var d=c.data(this[a],"olddisplay");!d&&d!=="none"&&c.data(this[a],
+"olddisplay",c.css(this[a],"display"))}a=0;for(b=this.length;a<b;a++)this[a].style.display="none";return this}},_toggle:c.fn.toggle,toggle:function(a,b){var d=typeof a==="boolean";if(c.isFunction(a)&&c.isFunction(b))this._toggle.apply(this,arguments);else a==null||d?this.each(function(){var f=d?a:c(this).is(":hidden");c(this)[f?"show":"hide"]()}):this.animate(K("toggle",3),a,b);return this},fadeTo:function(a,b,d){return this.filter(":hidden").css("opacity",0).show().end().animate({opacity:b},a,d)},
+animate:function(a,b,d,f){var e=c.speed(b,d,f);if(c.isEmptyObject(a))return this.each(e.complete);return this[e.queue===false?"each":"queue"](function(){var j=c.extend({},e),i,o=this.nodeType===1&&c(this).is(":hidden"),k=this;for(i in a){var n=i.replace(ia,ja);if(i!==n){a[n]=a[i];delete a[i];i=n}if(a[i]==="hide"&&o||a[i]==="show"&&!o)return j.complete.call(this);if((i==="height"||i==="width")&&this.style){j.display=c.css(this,"display");j.overflow=this.style.overflow}if(c.isArray(a[i])){(j.specialEasing=
+j.specialEasing||{})[i]=a[i][1];a[i]=a[i][0]}}if(j.overflow!=null)this.style.overflow="hidden";j.curAnim=c.extend({},a);c.each(a,function(r,u){var z=new c.fx(k,j,r);if(Ab.test(u))z[u==="toggle"?o?"show":"hide":u](a);else{var C=Bb.exec(u),B=z.cur(true)||0;if(C){u=parseFloat(C[2]);var E=C[3]||"px";if(E!=="px"){k.style[r]=(u||1)+E;B=(u||1)/z.cur(true)*B;k.style[r]=B+E}if(C[1])u=(C[1]==="-="?-1:1)*u+B;z.custom(B,u,E)}else z.custom(B,u,"")}});return true})},stop:function(a,b){var d=c.timers;a&&this.queue([]);
+this.each(function(){for(var f=d.length-1;f>=0;f--)if(d[f].elem===this){b&&d[f](true);d.splice(f,1)}});b||this.dequeue();return this}});c.each({slideDown:K("show",1),slideUp:K("hide",1),slideToggle:K("toggle",1),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"}},function(a,b){c.fn[a]=function(d,f){return this.animate(b,d,f)}});c.extend({speed:function(a,b,d){var f=a&&typeof a==="object"?a:{complete:d||!d&&b||c.isFunction(a)&&a,duration:a,easing:d&&b||b&&!c.isFunction(b)&&b};f.duration=c.fx.off?0:typeof f.duration===
+"number"?f.duration:c.fx.speeds[f.duration]||c.fx.speeds._default;f.old=f.complete;f.complete=function(){f.queue!==false&&c(this).dequeue();c.isFunction(f.old)&&f.old.call(this)};return f},easing:{linear:function(a,b,d,f){return d+f*a},swing:function(a,b,d,f){return(-Math.cos(a*Math.PI)/2+0.5)*f+d}},timers:[],fx:function(a,b,d){this.options=b;this.elem=a;this.prop=d;if(!b.orig)b.orig={}}});c.fx.prototype={update:function(){this.options.step&&this.options.step.call(this.elem,this.now,this);(c.fx.step[this.prop]||
+c.fx.step._default)(this);if((this.prop==="height"||this.prop==="width")&&this.elem.style)this.elem.style.display="block"},cur:function(a){if(this.elem[this.prop]!=null&&(!this.elem.style||this.elem.style[this.prop]==null))return this.elem[this.prop];return(a=parseFloat(c.css(this.elem,this.prop,a)))&&a>-10000?a:parseFloat(c.curCSS(this.elem,this.prop))||0},custom:function(a,b,d){function f(j){return e.step(j)}this.startTime=J();this.start=a;this.end=b;this.unit=d||this.unit||"px";this.now=this.start;
+this.pos=this.state=0;var e=this;f.elem=this.elem;if(f()&&c.timers.push(f)&&!W)W=setInterval(c.fx.tick,13)},show:function(){this.options.orig[this.prop]=c.style(this.elem,this.prop);this.options.show=true;this.custom(this.prop==="width"||this.prop==="height"?1:0,this.cur());c(this.elem).show()},hide:function(){this.options.orig[this.prop]=c.style(this.elem,this.prop);this.options.hide=true;this.custom(this.cur(),0)},step:function(a){var b=J(),d=true;if(a||b>=this.options.duration+this.startTime){this.now=
+this.end;this.pos=this.state=1;this.update();this.options.curAnim[this.prop]=true;for(var f in this.options.curAnim)if(this.options.curAnim[f]!==true)d=false;if(d){if(this.options.display!=null){this.elem.style.overflow=this.options.overflow;a=c.data(this.elem,"olddisplay");this.elem.style.display=a?a:this.options.display;if(c.css(this.elem,"display")==="none")this.elem.style.display="block"}this.options.hide&&c(this.elem).hide();if(this.options.hide||this.options.show)for(var e in this.options.curAnim)c.style(this.elem,
+e,this.options.orig[e]);this.options.complete.call(this.elem)}return false}else{e=b-this.startTime;this.state=e/this.options.duration;a=this.options.easing||(c.easing.swing?"swing":"linear");this.pos=c.easing[this.options.specialEasing&&this.options.specialEasing[this.prop]||a](this.state,e,0,1,this.options.duration);this.now=this.start+(this.end-this.start)*this.pos;this.update()}return true}};c.extend(c.fx,{tick:function(){for(var a=c.timers,b=0;b<a.length;b++)a[b]()||a.splice(b--,1);a.length||
+c.fx.stop()},stop:function(){clearInterval(W);W=null},speeds:{slow:600,fast:200,_default:400},step:{opacity:function(a){c.style(a.elem,"opacity",a.now)},_default:function(a){if(a.elem.style&&a.elem.style[a.prop]!=null)a.elem.style[a.prop]=(a.prop==="width"||a.prop==="height"?Math.max(0,a.now):a.now)+a.unit;else a.elem[a.prop]=a.now}}});if(c.expr&&c.expr.filters)c.expr.filters.animated=function(a){return c.grep(c.timers,function(b){return a===b.elem}).length};c.fn.offset="getBoundingClientRect"in s.documentElement?
+function(a){var b=this[0];if(a)return this.each(function(e){c.offset.setOffset(this,a,e)});if(!b||!b.ownerDocument)return null;if(b===b.ownerDocument.body)return c.offset.bodyOffset(b);var d=b.getBoundingClientRect(),f=b.ownerDocument;b=f.body;f=f.documentElement;return{top:d.top+(self.pageYOffset||c.support.boxModel&&f.scrollTop||b.scrollTop)-(f.clientTop||b.clientTop||0),left:d.left+(self.pageXOffset||c.support.boxModel&&f.scrollLeft||b.scrollLeft)-(f.clientLeft||b.clientLeft||0)}}:function(a){var b=
+this[0];if(a)return this.each(function(r){c.offset.setOffset(this,a,r)});if(!b||!b.ownerDocument)return null;if(b===b.ownerDocument.body)return c.offset.bodyOffset(b);c.offset.initialize();var d=b.offsetParent,f=b,e=b.ownerDocument,j,i=e.documentElement,o=e.body;f=(e=e.defaultView)?e.getComputedStyle(b,null):b.currentStyle;for(var k=b.offsetTop,n=b.offsetLeft;(b=b.parentNode)&&b!==o&&b!==i;){if(c.offset.supportsFixedPosition&&f.position==="fixed")break;j=e?e.getComputedStyle(b,null):b.currentStyle;
+k-=b.scrollTop;n-=b.scrollLeft;if(b===d){k+=b.offsetTop;n+=b.offsetLeft;if(c.offset.doesNotAddBorder&&!(c.offset.doesAddBorderForTableAndCells&&/^t(able|d|h)$/i.test(b.nodeName))){k+=parseFloat(j.borderTopWidth)||0;n+=parseFloat(j.borderLeftWidth)||0}f=d;d=b.offsetParent}if(c.offset.subtractsBorderForOverflowNotVisible&&j.overflow!=="visible"){k+=parseFloat(j.borderTopWidth)||0;n+=parseFloat(j.borderLeftWidth)||0}f=j}if(f.position==="relative"||f.position==="static"){k+=o.offsetTop;n+=o.offsetLeft}if(c.offset.supportsFixedPosition&&
+f.position==="fixed"){k+=Math.max(i.scrollTop,o.scrollTop);n+=Math.max(i.scrollLeft,o.scrollLeft)}return{top:k,left:n}};c.offset={initialize:function(){var a=s.body,b=s.createElement("div"),d,f,e,j=parseFloat(c.curCSS(a,"marginTop",true))||0;c.extend(b.style,{position:"absolute",top:0,left:0,margin:0,border:0,width:"1px",height:"1px",visibility:"hidden"});b.innerHTML="<div style='position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;'><div></div></div><table style='position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;' cellpadding='0' cellspacing='0'><tr><td></td></tr></table>";
+a.insertBefore(b,a.firstChild);d=b.firstChild;f=d.firstChild;e=d.nextSibling.firstChild.firstChild;this.doesNotAddBorder=f.offsetTop!==5;this.doesAddBorderForTableAndCells=e.offsetTop===5;f.style.position="fixed";f.style.top="20px";this.supportsFixedPosition=f.offsetTop===20||f.offsetTop===15;f.style.position=f.style.top="";d.style.overflow="hidden";d.style.position="relative";this.subtractsBorderForOverflowNotVisible=f.offsetTop===-5;this.doesNotIncludeMarginInBodyOffset=a.offsetTop!==j;a.removeChild(b);
+c.offset.initialize=c.noop},bodyOffset:function(a){var b=a.offsetTop,d=a.offsetLeft;c.offset.initialize();if(c.offset.doesNotIncludeMarginInBodyOffset){b+=parseFloat(c.curCSS(a,"marginTop",true))||0;d+=parseFloat(c.curCSS(a,"marginLeft",true))||0}return{top:b,left:d}},setOffset:function(a,b,d){if(/static/.test(c.curCSS(a,"position")))a.style.position="relative";var f=c(a),e=f.offset(),j=parseInt(c.curCSS(a,"top",true),10)||0,i=parseInt(c.curCSS(a,"left",true),10)||0;if(c.isFunction(b))b=b.call(a,
+d,e);d={top:b.top-e.top+j,left:b.left-e.left+i};"using"in b?b.using.call(a,d):f.css(d)}};c.fn.extend({position:function(){if(!this[0])return null;var a=this[0],b=this.offsetParent(),d=this.offset(),f=/^body|html$/i.test(b[0].nodeName)?{top:0,left:0}:b.offset();d.top-=parseFloat(c.curCSS(a,"marginTop",true))||0;d.left-=parseFloat(c.curCSS(a,"marginLeft",true))||0;f.top+=parseFloat(c.curCSS(b[0],"borderTopWidth",true))||0;f.left+=parseFloat(c.curCSS(b[0],"borderLeftWidth",true))||0;return{top:d.top-
+f.top,left:d.left-f.left}},offsetParent:function(){return this.map(function(){for(var a=this.offsetParent||s.body;a&&!/^body|html$/i.test(a.nodeName)&&c.css(a,"position")==="static";)a=a.offsetParent;return a})}});c.each(["Left","Top"],function(a,b){var d="scroll"+b;c.fn[d]=function(f){var e=this[0],j;if(!e)return null;if(f!==w)return this.each(function(){if(j=wa(this))j.scrollTo(!a?f:c(j).scrollLeft(),a?f:c(j).scrollTop());else this[d]=f});else return(j=wa(e))?"pageXOffset"in j?j[a?"pageYOffset":
+"pageXOffset"]:c.support.boxModel&&j.document.documentElement[d]||j.document.body[d]:e[d]}});c.each(["Height","Width"],function(a,b){var d=b.toLowerCase();c.fn["inner"+b]=function(){return this[0]?c.css(this[0],d,false,"padding"):null};c.fn["outer"+b]=function(f){return this[0]?c.css(this[0],d,false,f?"margin":"border"):null};c.fn[d]=function(f){var e=this[0];if(!e)return f==null?null:this;if(c.isFunction(f))return this.each(function(j){var i=c(this);i[d](f.call(this,j,i[d]()))});return"scrollTo"in
+e&&e.document?e.document.compatMode==="CSS1Compat"&&e.document.documentElement["client"+b]||e.document.body["client"+b]:e.nodeType===9?Math.max(e.documentElement["client"+b],e.body["scroll"+b],e.documentElement["scroll"+b],e.body["offset"+b],e.documentElement["offset"+b]):f===w?c.css(e,d):this.css(d,typeof f==="string"?f:f+"px")}});A.jQuery=A.$=c})(window);
diff --git a/lib/common_test/priv/jquery.tablesorter.min.js b/lib/common_test/priv/jquery.tablesorter.min.js
new file mode 100644
index 0000000000..b8605df1e7
--- /dev/null
+++ b/lib/common_test/priv/jquery.tablesorter.min.js
@@ -0,0 +1,4 @@
+
+(function($){$.extend({tablesorter:new
+function(){var parsers=[],widgets=[];this.defaults={cssHeader:"header",cssAsc:"headerSortUp",cssDesc:"headerSortDown",cssChildRow:"expand-child",sortInitialOrder:"asc",sortMultiSortKey:"shiftKey",sortForce:null,sortAppend:null,sortLocaleCompare:true,textExtraction:"simple",parsers:{},widgets:[],widgetZebra:{css:["even","odd"]},headers:{},widthFixed:false,cancelSelection:true,sortList:[],headerList:[],dateFormat:"us",decimal:'/\.|\,/g',onRenderHeader:null,selectorHeaders:'thead th',debug:false};function benchmark(s,d){log(s+","+(new Date().getTime()-d.getTime())+"ms");}this.benchmark=benchmark;function log(s){if(typeof console!="undefined"&&typeof console.debug!="undefined"){console.log(s);}else{alert(s);}}function buildParserCache(table,$headers){if(table.config.debug){var parsersDebug="";}if(table.tBodies.length==0)return;var rows=table.tBodies[0].rows;if(rows[0]){var list=[],cells=rows[0].cells,l=cells.length;for(var i=0;i<l;i++){var p=false;if($.metadata&&($($headers[i]).metadata()&&$($headers[i]).metadata().sorter)){p=getParserById($($headers[i]).metadata().sorter);}else if((table.config.headers[i]&&table.config.headers[i].sorter)){p=getParserById(table.config.headers[i].sorter);}if(!p){p=detectParserForColumn(table,rows,-1,i);}if(table.config.debug){parsersDebug+="column:"+i+" parser:"+p.id+"\n";}list.push(p);}}if(table.config.debug){log(parsersDebug);}return list;};function detectParserForColumn(table,rows,rowIndex,cellIndex){var l=parsers.length,node=false,nodeValue=false,keepLooking=true;while(nodeValue==''&&keepLooking){rowIndex++;if(rows[rowIndex]){node=getNodeFromRowAndCellIndex(rows,rowIndex,cellIndex);nodeValue=trimAndGetNodeText(table.config,node);if(table.config.debug){log('Checking if value was empty on row:'+rowIndex);}}else{keepLooking=false;}}for(var i=1;i<l;i++){if(parsers[i].is(nodeValue,table,node)){return parsers[i];}}return parsers[0];}function getNodeFromRowAndCellIndex(rows,rowIndex,cellIndex){return rows[rowIndex].cells[cellIndex];}function trimAndGetNodeText(config,node){return $.trim(getElementText(config,node));}function getParserById(name){var l=parsers.length;for(var i=0;i<l;i++){if(parsers[i].id.toLowerCase()==name.toLowerCase()){return parsers[i];}}return false;}function buildCache(table){if(table.config.debug){var cacheTime=new Date();}var totalRows=(table.tBodies[0]&&table.tBodies[0].rows.length)||0,totalCells=(table.tBodies[0].rows[0]&&table.tBodies[0].rows[0].cells.length)||0,parsers=table.config.parsers,cache={row:[],normalized:[]};for(var i=0;i<totalRows;++i){var c=$(table.tBodies[0].rows[i]),cols=[];if(c.hasClass(table.config.cssChildRow)){cache.row[cache.row.length-1]=cache.row[cache.row.length-1].add(c);continue;}cache.row.push(c);for(var j=0;j<totalCells;++j){cols.push(parsers[j].format(getElementText(table.config,c[0].cells[j]),table,c[0].cells[j]));}cols.push(cache.normalized.length);cache.normalized.push(cols);cols=null;};if(table.config.debug){benchmark("Building cache for "+totalRows+" rows:",cacheTime);}return cache;};function getElementText(config,node){var text="";if(!node)return"";if(!config.supportsTextContent)config.supportsTextContent=node.textContent||false;if(config.textExtraction=="simple"){if(config.supportsTextContent){text=node.textContent;}else{if(node.childNodes[0]&&node.childNodes[0].hasChildNodes()){text=node.childNodes[0].innerHTML;}else{text=node.innerHTML;}}}else{if(typeof(config.textExtraction)=="function"){text=config.textExtraction(node);}else{text=$(node).text();}}return text;}function appendToTable(table,cache){if(table.config.debug){var appendTime=new Date()}var c=cache,r=c.row,n=c.normalized,totalRows=n.length,checkCell=(n[0].length-1),tableBody=$(table.tBodies[0]),rows=[];for(var i=0;i<totalRows;i++){var pos=n[i][checkCell];rows.push(r[pos]);if(!table.config.appender){var l=r[pos].length;for(var j=0;j<l;j++){tableBody[0].appendChild(r[pos][j]);}}}if(table.config.appender){table.config.appender(table,rows);}rows=null;if(table.config.debug){benchmark("Rebuilt table:",appendTime);}applyWidget(table);setTimeout(function(){$(table).trigger("sortEnd");},0);};function buildHeaders(table){if(table.config.debug){var time=new Date();}var meta=($.metadata)?true:false;var header_index=computeTableHeaderCellIndexes(table);$tableHeaders=$(table.config.selectorHeaders,table).each(function(index){this.column=header_index[this.parentNode.rowIndex+"-"+this.cellIndex];this.order=formatSortingOrder(table.config.sortInitialOrder);this.count=this.order;if(checkHeaderMetadata(this)||checkHeaderOptions(table,index))this.sortDisabled=true;if(checkHeaderOptionsSortingLocked(table,index))this.order=this.lockedOrder=checkHeaderOptionsSortingLocked(table,index);if(!this.sortDisabled){var $th=$(this).addClass(table.config.cssHeader);if(table.config.onRenderHeader)table.config.onRenderHeader.apply($th);}table.config.headerList[index]=this;});if(table.config.debug){benchmark("Built headers:",time);log($tableHeaders);}return $tableHeaders;};function computeTableHeaderCellIndexes(t){var matrix=[];var lookup={};var thead=t.getElementsByTagName('THEAD')[0];var trs=thead.getElementsByTagName('TR');for(var i=0;i<trs.length;i++){var cells=trs[i].cells;for(var j=0;j<cells.length;j++){var c=cells[j];var rowIndex=c.parentNode.rowIndex;var cellId=rowIndex+"-"+c.cellIndex;var rowSpan=c.rowSpan||1;var colSpan=c.colSpan||1
+var firstAvailCol;if(typeof(matrix[rowIndex])=="undefined"){matrix[rowIndex]=[];}for(var k=0;k<matrix[rowIndex].length+1;k++){if(typeof(matrix[rowIndex][k])=="undefined"){firstAvailCol=k;break;}}lookup[cellId]=firstAvailCol;for(var k=rowIndex;k<rowIndex+rowSpan;k++){if(typeof(matrix[k])=="undefined"){matrix[k]=[];}var matrixrow=matrix[k];for(var l=firstAvailCol;l<firstAvailCol+colSpan;l++){matrixrow[l]="x";}}}}return lookup;}function checkCellColSpan(table,rows,row){var arr=[],r=table.tHead.rows,c=r[row].cells;for(var i=0;i<c.length;i++){var cell=c[i];if(cell.colSpan>1){arr=arr.concat(checkCellColSpan(table,headerArr,row++));}else{if(table.tHead.length==1||(cell.rowSpan>1||!r[row+1])){arr.push(cell);}}}return arr;};function checkHeaderMetadata(cell){if(($.metadata)&&($(cell).metadata().sorter===false)){return true;};return false;}function checkHeaderOptions(table,i){if((table.config.headers[i])&&(table.config.headers[i].sorter===false)){return true;};return false;}function checkHeaderOptionsSortingLocked(table,i){if((table.config.headers[i])&&(table.config.headers[i].lockedOrder))return table.config.headers[i].lockedOrder;return false;}function applyWidget(table){var c=table.config.widgets;var l=c.length;for(var i=0;i<l;i++){getWidgetById(c[i]).format(table);}}function getWidgetById(name){var l=widgets.length;for(var i=0;i<l;i++){if(widgets[i].id.toLowerCase()==name.toLowerCase()){return widgets[i];}}};function formatSortingOrder(v){if(typeof(v)!="Number"){return(v.toLowerCase()=="desc")?1:0;}else{return(v==1)?1:0;}}function isValueInArray(v,a){var l=a.length;for(var i=0;i<l;i++){if(a[i][0]==v){return true;}}return false;}function setHeadersCss(table,$headers,list,css){$headers.removeClass(css[0]).removeClass(css[1]);var h=[];$headers.each(function(offset){if(!this.sortDisabled){h[this.column]=$(this);}});var l=list.length;for(var i=0;i<l;i++){h[list[i][0]].addClass(css[list[i][1]]);}}function fixColumnWidth(table,$headers){var c=table.config;if(c.widthFixed){var colgroup=$('<colgroup>');$("tr:first td",table.tBodies[0]).each(function(){colgroup.append($('<col>').css('width',$(this).width()));});$(table).prepend(colgroup);};}function updateHeaderSortCount(table,sortList){var c=table.config,l=sortList.length;for(var i=0;i<l;i++){var s=sortList[i],o=c.headerList[s[0]];o.count=s[1];o.count++;}}function multisort(table,sortList,cache){if(table.config.debug){var sortTime=new Date();}var dynamicExp="var sortWrapper = function(a,b) {",l=sortList.length;for(var i=0;i<l;i++){var c=sortList[i][0];var order=sortList[i][1];var s=(table.config.parsers[c].type=="text")?((order==0)?makeSortFunction("text","asc",c):makeSortFunction("text","desc",c)):((order==0)?makeSortFunction("numeric","asc",c):makeSortFunction("numeric","desc",c));var e="e"+i;dynamicExp+="var "+e+" = "+s;dynamicExp+="if("+e+") { return "+e+"; } ";dynamicExp+="else { ";}var orgOrderCol=cache.normalized[0].length-1;dynamicExp+="return a["+orgOrderCol+"]-b["+orgOrderCol+"];";for(var i=0;i<l;i++){dynamicExp+="}; ";}dynamicExp+="return 0; ";dynamicExp+="}; ";if(table.config.debug){benchmark("Evaling expression:"+dynamicExp,new Date());}eval(dynamicExp);cache.normalized.sort(sortWrapper);if(table.config.debug){benchmark("Sorting on "+sortList.toString()+" and dir "+order+" time:",sortTime);}return cache;};function makeSortFunction(type,direction,index){var a="a["+index+"]",b="b["+index+"]";if(type=='text'&&direction=='asc'){return"("+a+" == "+b+" ? 0 : ("+a+" === null ? Number.POSITIVE_INFINITY : ("+b+" === null ? Number.NEGATIVE_INFINITY : ("+a+" < "+b+") ? -1 : 1 )));";}else if(type=='text'&&direction=='desc'){return"("+a+" == "+b+" ? 0 : ("+a+" === null ? Number.POSITIVE_INFINITY : ("+b+" === null ? Number.NEGATIVE_INFINITY : ("+b+" < "+a+") ? -1 : 1 )));";}else if(type=='numeric'&&direction=='asc'){return"("+a+" === null && "+b+" === null) ? 0 :("+a+" === null ? Number.POSITIVE_INFINITY : ("+b+" === null ? Number.NEGATIVE_INFINITY : "+a+" - "+b+"));";}else if(type=='numeric'&&direction=='desc'){return"("+a+" === null && "+b+" === null) ? 0 :("+a+" === null ? Number.POSITIVE_INFINITY : ("+b+" === null ? Number.NEGATIVE_INFINITY : "+b+" - "+a+"));";}};function makeSortText(i){return"((a["+i+"] < b["+i+"]) ? -1 : ((a["+i+"] > b["+i+"]) ? 1 : 0));";};function makeSortTextDesc(i){return"((b["+i+"] < a["+i+"]) ? -1 : ((b["+i+"] > a["+i+"]) ? 1 : 0));";};function makeSortNumeric(i){return"a["+i+"]-b["+i+"];";};function makeSortNumericDesc(i){return"b["+i+"]-a["+i+"];";};function sortText(a,b){if(table.config.sortLocaleCompare)return a.localeCompare(b);return((a<b)?-1:((a>b)?1:0));};function sortTextDesc(a,b){if(table.config.sortLocaleCompare)return b.localeCompare(a);return((b<a)?-1:((b>a)?1:0));};function sortNumeric(a,b){return a-b;};function sortNumericDesc(a,b){return b-a;};function getCachedSortType(parsers,i){return parsers[i].type;};this.construct=function(settings){return this.each(function(){if(!this.tHead||!this.tBodies)return;var $this,$document,$headers,cache,config,shiftDown=0,sortOrder;this.config={};config=$.extend(this.config,$.tablesorter.defaults,settings);$this=$(this);$.data(this,"tablesorter",config);$headers=buildHeaders(this);this.config.parsers=buildParserCache(this,$headers);cache=buildCache(this);var sortCSS=[config.cssDesc,config.cssAsc];fixColumnWidth(this);$headers.click(function(e){var totalRows=($this[0].tBodies[0]&&$this[0].tBodies[0].rows.length)||0;if(!this.sortDisabled&&totalRows>0){$this.trigger("sortStart");var $cell=$(this);var i=this.column;this.order=this.count++%2;if(this.lockedOrder)this.order=this.lockedOrder;if(!e[config.sortMultiSortKey]){config.sortList=[];if(config.sortForce!=null){var a=config.sortForce;for(var j=0;j<a.length;j++){if(a[j][0]!=i){config.sortList.push(a[j]);}}}config.sortList.push([i,this.order]);}else{if(isValueInArray(i,config.sortList)){for(var j=0;j<config.sortList.length;j++){var s=config.sortList[j],o=config.headerList[s[0]];if(s[0]==i){o.count=s[1];o.count++;s[1]=o.count%2;}}}else{config.sortList.push([i,this.order]);}};setTimeout(function(){setHeadersCss($this[0],$headers,config.sortList,sortCSS);appendToTable($this[0],multisort($this[0],config.sortList,cache));},1);return false;}}).mousedown(function(){if(config.cancelSelection){this.onselectstart=function(){return false};return false;}});$this.bind("update",function(){var me=this;setTimeout(function(){me.config.parsers=buildParserCache(me,$headers);cache=buildCache(me);},1);}).bind("updateCell",function(e,cell){var config=this.config;var pos=[(cell.parentNode.rowIndex-1),cell.cellIndex];cache.normalized[pos[0]][pos[1]]=config.parsers[pos[1]].format(getElementText(config,cell),cell);}).bind("sorton",function(e,list){$(this).trigger("sortStart");config.sortList=list;var sortList=config.sortList;updateHeaderSortCount(this,sortList);setHeadersCss(this,$headers,sortList,sortCSS);appendToTable(this,multisort(this,sortList,cache));}).bind("appendCache",function(){appendToTable(this,cache);}).bind("applyWidgetId",function(e,id){getWidgetById(id).format(this);}).bind("applyWidgets",function(){applyWidget(this);});if($.metadata&&($(this).metadata()&&$(this).metadata().sortlist)){config.sortList=$(this).metadata().sortlist;}if(config.sortList.length>0){$this.trigger("sorton",[config.sortList]);}applyWidget(this);});};this.addParser=function(parser){var l=parsers.length,a=true;for(var i=0;i<l;i++){if(parsers[i].id.toLowerCase()==parser.id.toLowerCase()){a=false;}}if(a){parsers.push(parser);};};this.addWidget=function(widget){widgets.push(widget);};this.formatFloat=function(s){var i=parseFloat(s);return(isNaN(i))?0:i;};this.formatInt=function(s){var i=parseInt(s);return(isNaN(i))?0:i;};this.isDigit=function(s,config){return/^[-+]?\d*$/.test($.trim(s.replace(/[,.']/g,'')));};this.clearTableBody=function(table){if($.browser.msie){function empty(){while(this.firstChild)this.removeChild(this.firstChild);}empty.apply(table.tBodies[0]);}else{table.tBodies[0].innerHTML="";}};}});$.fn.extend({tablesorter:$.tablesorter.construct});var ts=$.tablesorter;ts.addParser({id:"text",is:function(s){return true;},format:function(s){return $.trim(s.toLocaleLowerCase());},type:"text"});ts.addParser({id:"digit",is:function(s,table){var c=table.config;return $.tablesorter.isDigit(s,c);},format:function(s){return $.tablesorter.formatFloat(s);},type:"numeric"});ts.addParser({id:"currency",is:function(s){return/^[£$€?.]/.test(s);},format:function(s){return $.tablesorter.formatFloat(s.replace(new RegExp(/[£$€]/g),""));},type:"numeric"});ts.addParser({id:"ipAddress",is:function(s){return/^\d{2,3}[\.]\d{2,3}[\.]\d{2,3}[\.]\d{2,3}$/.test(s);},format:function(s){var a=s.split("."),r="",l=a.length;for(var i=0;i<l;i++){var item=a[i];if(item.length==2){r+="0"+item;}else{r+=item;}}return $.tablesorter.formatFloat(r);},type:"numeric"});ts.addParser({id:"url",is:function(s){return/^(https?|ftp|file):\/\/$/.test(s);},format:function(s){return jQuery.trim(s.replace(new RegExp(/(https?|ftp|file):\/\//),''));},type:"text"});ts.addParser({id:"isoDate",is:function(s){return/^\d{4}[\/-]\d{1,2}[\/-]\d{1,2}$/.test(s);},format:function(s){return $.tablesorter.formatFloat((s!="")?new Date(s.replace(new RegExp(/-/g),"/")).getTime():"0");},type:"numeric"});ts.addParser({id:"percent",is:function(s){return/\%$/.test($.trim(s));},format:function(s){return $.tablesorter.formatFloat(s.replace(new RegExp(/%/g),""));},type:"numeric"});ts.addParser({id:"usLongDate",is:function(s){return s.match(new RegExp(/^[A-Za-z]{3,10}\.? [0-9]{1,2}, ([0-9]{4}|'?[0-9]{2}) (([0-2]?[0-9]:[0-5][0-9])|([0-1]?[0-9]:[0-5][0-9]\s(AM|PM)))$/));},format:function(s){return $.tablesorter.formatFloat(new Date(s).getTime());},type:"numeric"});ts.addParser({id:"shortDate",is:function(s){return/\d{1,2}[\/\-]\d{1,2}[\/\-]\d{2,4}/.test(s);},format:function(s,table){var c=table.config;s=s.replace(/\-/g,"/");if(c.dateFormat=="us"){s=s.replace(/(\d{1,2})[\/\-](\d{1,2})[\/\-](\d{4})/,"$3/$1/$2");}else if(c.dateFormat=="uk"){s=s.replace(/(\d{1,2})[\/\-](\d{1,2})[\/\-](\d{4})/,"$3/$2/$1");}else if(c.dateFormat=="dd/mm/yy"||c.dateFormat=="dd-mm-yy"){s=s.replace(/(\d{1,2})[\/\-](\d{1,2})[\/\-](\d{2})/,"$1/$2/$3");}return $.tablesorter.formatFloat(new Date(s).getTime());},type:"numeric"});ts.addParser({id:"time",is:function(s){return/^(([0-2]?[0-9]:[0-5][0-9])|([0-1]?[0-9]:[0-5][0-9]\s(am|pm)))$/.test(s);},format:function(s){return $.tablesorter.formatFloat(new Date("2000/01/01 "+s).getTime());},type:"numeric"});ts.addParser({id:"metadata",is:function(s){return false;},format:function(s,table,cell){var c=table.config,p=(!c.parserMetadataName)?'sortValue':c.parserMetadataName;return $(cell).metadata()[p];},type:"numeric"});ts.addWidget({id:"zebra",format:function(table){if(table.config.debug){var time=new Date();}var $tr,row=-1,odd;$("tr:visible",table.tBodies[0]).each(function(i){$tr=$(this);if(!$tr.hasClass(table.config.cssChildRow))row++;odd=(row%2==0);$tr.removeClass(table.config.widgetZebra.css[odd?0:1]).addClass(table.config.widgetZebra.css[odd?1:0])});if(table.config.debug){$.tablesorter.benchmark("Applying Zebra widget",time);}}});})(jQuery); \ No newline at end of file
diff --git a/lib/common_test/src/ct.erl b/lib/common_test/src/ct.erl
index 6373634812..8d4721ab63 100644
--- a/lib/common_test/src/ct.erl
+++ b/lib/common_test/src/ct.erl
@@ -51,6 +51,8 @@
-module(ct).
+-include("ct.hrl").
+
%% Command line user interface for running tests
-export([install/1, run/1, run/2, run/3,
run_test/1, run_testspec/1, step/3, step/4,
@@ -60,14 +62,15 @@
-export([require/1, require/2,
get_config/1, get_config/2, get_config/3,
reload_config/1,
- log/1, log/2, log/3,
- print/1, print/2, print/3,
- pal/1, pal/2, pal/3,
+ log/1, log/2, log/3, log/4,
+ print/1, print/2, print/3, print/4,
+ pal/1, pal/2, pal/3, pal/4,
capture_start/0, capture_stop/0, capture_get/0, capture_get/1,
fail/1, fail/2, comment/1, comment/2, make_priv_dir/0,
testcases/2, userdata/2, userdata/3,
timetrap/1, get_timetrap_info/0, sleep/1,
- notify/2, sync_notify/2]).
+ notify/2, sync_notify/2,
+ break/1, break/2, continue/0, continue/1]).
%% New API for manipulating with config handlers
-export([add_config/2, remove_config/2]).
@@ -151,8 +154,10 @@ run(TestDirs) ->
%%% {multiply_timetraps,M} | {scale_timetraps,Bool} |
%%% {repeat,N} | {duration,DurTime} | {until,StopTime} |
%%% {force_stop,Bool} | {decrypt,DecryptKeyOrFile} |
-%%% {refresh_logs,LogDir} | {logopts,LogOpts} | {basic_html,Bool} |
-%%% {ct_hooks, CTHs} | {enable_builtin_hooks,Bool}
+%%% {refresh_logs,LogDir} | {logopts,LogOpts} |
+%%% {verbosity,VLevels} | {basic_html,Bool} |
+%%% {ct_hooks, CTHs} | {enable_builtin_hooks,Bool} |
+%%% {noinput,Bool}
%%% TestDirs = [string()] | string()
%%% Suites = [string()] | [atom()] | string() | atom()
%%% Cases = [atom()] | atom()
@@ -183,6 +188,9 @@ run(TestDirs) ->
%%% DecryptFile = string()
%%% LogOpts = [LogOpt]
%%% LogOpt = no_nl | no_src
+%%% VLevels = VLevel | [{Category,VLevel}]
+%%% VLevel = integer()
+%%% Category = atom()
%%% CTHs = [CTHModule | {CTHModule, CTHInitArgs}]
%%% CTHModule = atom()
%%% CTHInitArgs = term()
@@ -422,25 +430,41 @@ reload_config(Required)->
%%%-----------------------------------------------------------------
%%% @spec log(Format) -> ok
-%%% @equiv log(default,Format,[])
+%%% @equiv log(default,50,Format,[])
log(Format) ->
- log(default,Format,[]).
+ log(default,?STD_IMPORTANCE,Format,[]).
%%%-----------------------------------------------------------------
%%% @spec log(X1,X2) -> ok
-%%% X1 = Category | Format
+%%% X1 = Category | Importance | Format
%%% X2 = Format | Args
-%%% @equiv log(Category,Format,Args)
+%%% @equiv log(Category,Importance,Format,Args)
log(X1,X2) ->
- {Category,Format,Args} =
- if is_atom(X1) -> {X1,X2,[]};
- is_list(X1) -> {default,X1,X2}
+ {Category,Importance,Format,Args} =
+ if is_atom(X1) -> {X1,?STD_IMPORTANCE,X2,[]};
+ is_integer(X1) -> {default,X1,X2,[]};
+ is_list(X1) -> {default,?STD_IMPORTANCE,X1,X2}
+ end,
+ log(Category,Importance,Format,Args).
+
+%%%-----------------------------------------------------------------
+%%% @spec log(X1,X2,X3) -> ok
+%%% X1 = Category | Importance
+%%% X2 = Importance | Format
+%%% X3 = Format | Args
+%%% @equiv log(Category,Importance,Format,Args)
+log(X1,X2,X3) ->
+ {Category,Importance,Format,Args} =
+ if is_atom(X1), is_integer(X2) -> {X1,X2,X3,[]};
+ is_atom(X1), is_list(X2) -> {X1,?STD_IMPORTANCE,X2,X3};
+ is_integer(X1) -> {default,X1,X2,X3}
end,
- log(Category,Format,Args).
+ log(Category,Importance,Format,Args).
%%%-----------------------------------------------------------------
-%%% @spec log(Category,Format,Args) -> ok
+%%% @spec log(Category,Importance,Format,Args) -> ok
%%% Category = atom()
+%%% Importance = integer()
%%% Format = string()
%%% Args = list()
%%%
@@ -449,30 +473,52 @@ log(X1,X2) ->
%%% <p>This function is meant for printing a string directly from a
%%% test case to the test case log file.</p>
%%%
-%%% <p>Default <code>Category</code> is <code>default</code> and
-%%% default <code>Args</code> is <code>[]</code>.</p>
-log(Category,Format,Args) ->
- ct_logs:tc_log(Category,Format,Args).
+%%% <p>Default <code>Category</code> is <code>default</code>,
+%%% default <code>Importance</code> is <code>?STD_IMPORTANCE</code>,
+%%% and default value for <code>Args</code> is <code>[]</code>.</p>
+%%% <p>Please see the User's Guide for details on <code>Category</code>
+%%% and <code>Importance</code>.</p>
+log(Category,Importance,Format,Args) ->
+ ct_logs:tc_log(Category,Importance,Format,Args).
%%%-----------------------------------------------------------------
%%% @spec print(Format) -> ok
-%%% @equiv print(default,Format,[])
+%%% @equiv print(default,50,Format,[])
print(Format) ->
- print(default,Format,[]).
+ print(default,?STD_IMPORTANCE,Format,[]).
%%%-----------------------------------------------------------------
-%%% @equiv print(Category,Format,Args)
+%%% @spec print(X1,X2) -> ok
+%%% X1 = Category | Importance | Format
+%%% X2 = Format | Args
+%%% @equiv print(Category,Importance,Format,Args)
print(X1,X2) ->
- {Category,Format,Args} =
- if is_atom(X1) -> {X1,X2,[]};
- is_list(X1) -> {default,X1,X2}
+ {Category,Importance,Format,Args} =
+ if is_atom(X1) -> {X1,?STD_IMPORTANCE,X2,[]};
+ is_integer(X1) -> {default,X1,X2,[]};
+ is_list(X1) -> {default,?STD_IMPORTANCE,X1,X2}
+ end,
+ print(Category,Importance,Format,Args).
+
+%%%-----------------------------------------------------------------
+%%% @spec print(X1,X2,X3) -> ok
+%%% X1 = Category | Importance
+%%% X2 = Importance | Format
+%%% X3 = Format | Args
+%%% @equiv print(Category,Importance,Format,Args)
+print(X1,X2,X3) ->
+ {Category,Importance,Format,Args} =
+ if is_atom(X1), is_integer(X2) -> {X1,X2,X3,[]};
+ is_atom(X1), is_list(X2) -> {X1,?STD_IMPORTANCE,X2,X3};
+ is_integer(X1) -> {default,X1,X2,X3}
end,
- print(Category,Format,Args).
+ print(Category,Importance,Format,Args).
%%%-----------------------------------------------------------------
-%%% @spec print(Category,Format,Args) -> ok
+%%% @spec print(Category,Importance,Format,Args) -> ok
%%% Category = atom()
+%%% Importance = integer()
%%% Format = string()
%%% Args = list()
%%%
@@ -481,33 +527,52 @@ print(X1,X2) ->
%%% <p>This function is meant for printing a string from a test case
%%% to the console.</p>
%%%
-%%% <p>Default <code>Category</code> is <code>default</code> and
-%%% default <code>Args</code> is <code>[]</code>.</p>
-print(Category,Format,Args) ->
- ct_logs:tc_print(Category,Format,Args).
+%%% <p>Default <code>Category</code> is <code>default</code>,
+%%% default <code>Importance</code> is <code>?STD_IMPORTANCE</code>,
+%%% and default value for <code>Args</code> is <code>[]</code>.</p>
+%%% <p>Please see the User's Guide for details on <code>Category</code>
+%%% and <code>Importance</code>.</p>
+print(Category,Importance,Format,Args) ->
+ ct_logs:tc_print(Category,Importance,Format,Args).
%%%-----------------------------------------------------------------
%%% @spec pal(Format) -> ok
-%%% @equiv pal(default,Format,[])
+%%% @equiv pal(default,50,Format,[])
pal(Format) ->
- pal(default,Format,[]).
+ pal(default,?STD_IMPORTANCE,Format,[]).
%%%-----------------------------------------------------------------
%%% @spec pal(X1,X2) -> ok
-%%% X1 = Category | Format
+%%% X1 = Category | Importance | Format
%%% X2 = Format | Args
-%%% @equiv pal(Category,Format,Args)
+%%% @equiv pal(Category,Importance,Format,Args)
pal(X1,X2) ->
- {Category,Format,Args} =
- if is_atom(X1) -> {X1,X2,[]};
- is_list(X1) -> {default,X1,X2}
+ {Category,Importance,Format,Args} =
+ if is_atom(X1) -> {X1,?STD_IMPORTANCE,X2,[]};
+ is_integer(X1) -> {default,X1,X2,[]};
+ is_list(X1) -> {default,?STD_IMPORTANCE,X1,X2}
end,
- pal(Category,Format,Args).
+ pal(Category,Importance,Format,Args).
%%%-----------------------------------------------------------------
-%%% @spec pal(Category,Format,Args) -> ok
+%%% @spec pal(X1,X2,X3) -> ok
+%%% X1 = Category | Importance
+%%% X2 = Importance | Format
+%%% X3 = Format | Args
+%%% @equiv pal(Category,Importance,Format,Args)
+pal(X1,X2,X3) ->
+ {Category,Importance,Format,Args} =
+ if is_atom(X1), is_integer(X2) -> {X1,X2,X3,[]};
+ is_atom(X1), is_list(X2) -> {X1,?STD_IMPORTANCE,X2,X3};
+ is_integer(X1) -> {default,X1,X2,X3}
+ end,
+ pal(Category,Importance,Format,Args).
+
+%%%-----------------------------------------------------------------
+%%% @spec pal(Category,Importance,Format,Args) -> ok
%%% Category = atom()
+%%% Importance = integer()
%%% Format = string()
%%% Args = list()
%%%
@@ -516,10 +581,13 @@ pal(X1,X2) ->
%%% <p>This function is meant for printing a string from a test case,
%%% both to the test case log file and to the console.</p>
%%%
-%%% <p>Default <code>Category</code> is <code>default</code> and
-%%% default <code>Args</code> is <code>[]</code>.</p>
-pal(Category,Format,Args) ->
- ct_logs:tc_pal(Category,Format,Args).
+%%% <p>Default <code>Category</code> is <code>default</code>,
+%%% default <code>Importance</code> is <code>?STD_IMPORTANCE</code>,
+%%% and default value for <code>Args</code> is <code>[]</code>.</p>
+%%% <p>Please see the User's Guide for details on <code>Category</code>
+%%% and <code>Importance</code>.</p>
+pal(Category,Importance,Format,Args) ->
+ ct_logs:tc_pal(Category,Importance,Format,Args).
%%%-----------------------------------------------------------------
%%% @spec capture_start() -> ok
@@ -841,8 +909,9 @@ userdata(TestDir, Suite, Case) when is_atom(Case) ->
%%%-----------------------------------------------------------------
%%% @spec get_status() -> TestStatus | {error,Reason} | no_tests_running
%%% TestStatus = [StatusElem]
-%%% StatusElem = {current,{Suite,TestCase}} | {successful,Successful} |
+%%% StatusElem = {current,TestCaseInfo} | {successful,Successful} |
%%% {failed,Failed} | {skipped,Skipped} | {total,Total}
+%%% TestCaseInfo = {Suite,TestCase} | [{Suite,TestCase}]
%%% Suite = atom()
%%% TestCase = atom()
%%% Successful = integer()
@@ -854,7 +923,8 @@ userdata(TestDir, Suite, Case) when is_atom(Case) ->
%%% Reason = term()
%%%
%%% @doc Returns status of ongoing test. The returned list contains info about
-%%% which test case is currently executing, as well as counters for
+%%% which test case is currently executing (a list of cases when a
+%%% parallel test case group is executing), as well as counters for
%%% successful, failed, skipped, and total test cases so far.
get_status() ->
case get_testdata(curr_tc) of
@@ -879,6 +949,8 @@ get_testdata(Key) ->
Error;
{'EXIT',_Reason} ->
no_tests_running;
+ [CurrTC] when Key == curr_tc ->
+ {ok,CurrTC};
Data ->
{ok,Data}
end.
@@ -1072,3 +1144,74 @@ notify(Name,Data) ->
%%% @see //stdlib/gen_event
sync_notify(Name,Data) ->
ct_event:sync_notify(Name, Data).
+
+%%%-----------------------------------------------------------------
+%%% @spec break(Comment) -> ok | {error,Reason}
+%%% Comment = string()
+%%% Reason = {multiple_cases_running,TestCases}
+%%% TestCases = [atom()]
+%%%
+%%% @doc <p>This function will cancel all timetraps and pause the
+%%% execution of the current test case until the user calls the
+%%% <c>continue/0</c> function. It gives the user the opportunity
+%%% to interact with the erlang node running the tests, e.g. for
+%%% debugging purposes or for manually executing a part of the
+%%% test case. If a parallel group is executing, <c>break/2</c>
+%%% should be called instead.</p>
+break(Comment) ->
+ case get_testdata(curr_tc) of
+ {ok,{_,TestCase}} ->
+ test_server:break(?MODULE, Comment);
+ {ok,Cases} when is_list(Cases) ->
+ {error,{multiple_cases_running,
+ [TC || {_,TC} <- Cases]}};
+ Error ->
+ {error,Error}
+ end.
+
+%%%-----------------------------------------------------------------
+%%% @spec break(TestCase, Comment) -> ok | {error,Reason}
+%%% TestCase = atom()
+%%% Comment = string()
+%%% Reason = test_case_not_running
+%%%
+%%% @doc <p>This function works the same way as <c>break/1</c>,
+%%% only the <c>TestCase</c> argument makes it possible to
+%%% pause a test case executing in a parallel group. The
+%%% <c>continue/1</c> function should be used to resume
+%%% execution of <c>TestCase</c>.</p>
+break(TestCase, Comment) ->
+ case get_testdata(curr_tc) of
+ {ok,Cases} when is_list(Cases) ->
+ case lists:keymember(TestCase, 2, Cases) of
+ true ->
+ test_server:break(?MODULE, TestCase, Comment);
+ false ->
+ {error,test_case_not_running}
+ end;
+ {ok,{_,TestCase}} ->
+ test_server:break(?MODULE, TestCase, Comment);
+ Error ->
+ {error,Error}
+ end.
+
+%%%-----------------------------------------------------------------
+%%% @spec continue() -> ok
+%%%
+%%% @doc <p>This function must be called in order to continue after a
+%%% test case (not executing in a parallel group) has called
+%%% <c>break/1</c>.</p>
+continue() ->
+ test_server:continue().
+
+%%%-----------------------------------------------------------------
+%%% @spec continue(TestCase) -> ok
+%%% TestCase = atom()
+%%%
+%%% @doc <p>This function must be called in order to continue after a
+%%% test case has called <c>break/2</c>. If the paused test case,
+%%% <c>TestCase</c>, executes in a parallel group, this
+%%% function - rather than <c>continue/0</c> - must be used
+%%% in order to let the test case proceed.</p>
+continue(TestCase) ->
+ test_server:continue(TestCase).
diff --git a/lib/common_test/src/ct_config.erl b/lib/common_test/src/ct_config.erl
index 9277af5bc1..c585fe0995 100644
--- a/lib/common_test/src/ct_config.erl
+++ b/lib/common_test/src/ct_config.erl
@@ -760,13 +760,13 @@ check_config_files(Configs) ->
end,
lists:keysearch(error, 1, lists:flatten(lists:map(ConfigChecker, Configs))).
-prepare_user_configs([ConfigString|UserConfigs], Acc, new) ->
+prepare_user_configs([CallbackMod|UserConfigs], Acc, new) ->
prepare_user_configs(UserConfigs,
- [{list_to_atom(ConfigString), []}|Acc],
+ [{list_to_atom(CallbackMod),[]}|Acc],
cur);
prepare_user_configs(["and"|UserConfigs], Acc, _) ->
prepare_user_configs(UserConfigs, Acc, new);
-prepare_user_configs([ConfigString|UserConfigs], [{LastMod, LastList}|Acc], cur) ->
+prepare_user_configs([ConfigString|UserConfigs], [{LastMod,LastList}|Acc], cur) ->
prepare_user_configs(UserConfigs,
[{LastMod, [ConfigString|LastList]}|Acc],
cur);
diff --git a/lib/common_test/src/ct_framework.erl b/lib/common_test/src/ct_framework.erl
index e53383e038..e8d5d4708b 100644
--- a/lib/common_test/src/ct_framework.erl
+++ b/lib/common_test/src/ct_framework.erl
@@ -27,7 +27,7 @@
-export([init_tc/3, end_tc/3, end_tc/4, get_suite/2, get_all_cases/1]).
-export([report/2, warn/1, error_notification/4]).
--export([get_logopts/0, format_comment/1, get_html_wrapper/3]).
+-export([get_logopts/0, format_comment/1, get_html_wrapper/4]).
-export([error_in_suite/1, init_per_suite/1, end_per_suite/1,
init_per_group/2, end_per_group/2]).
@@ -71,19 +71,29 @@ init_tc(Mod,Func,Config) ->
{skip,{require_failed_in_suite0,Reason}};
{Suite,{suite0_failed,_}=Failure} ->
{skip,Failure};
- _ ->
- ct_util:set_testdata({curr_tc,{Suite,Func}}),
+ CurrTC ->
+ case CurrTC of
+ undefined ->
+ ct_util:set_testdata({curr_tc,[{Suite,Func}]});
+ _ when is_list(CurrTC) ->
+ ct_util:update_testdata(curr_tc,
+ fun(Running) ->
+ [{Suite,Func}|Running]
+ end)
+ end,
case ct_util:read_suite_data({seq,Suite,Func}) of
undefined ->
init_tc1(Mod,Suite,Func,Config);
Seq when is_atom(Seq) ->
case ct_util:read_suite_data({seq,Suite,Seq}) of
[Func|TCs] -> % this is the 1st case in Seq
- %% make sure no cases in this seq are marked as failed
- %% from an earlier execution in the same suite
+ %% make sure no cases in this seq are
+ %% marked as failed from an earlier execution
+ %% in the same suite
lists:foreach(
fun(TC) ->
- ct_util:save_suite_data({seq,Suite,TC},Seq)
+ ct_util:save_suite_data({seq,Suite,TC},
+ Seq)
end, TCs);
_ ->
ok
@@ -206,7 +216,8 @@ init_tc2(Mod,Suite,Func,SuiteInfo,MergeResult,Config) ->
case catch configure(MergedInfo,MergedInfo,SuiteInfo,
FuncSpec,[],Config) of
{suite0_failed,Reason} ->
- ct_util:set_testdata({curr_tc,{Mod,{suite0_failed,{require,Reason}}}}),
+ ct_util:set_testdata({curr_tc,{Mod,{suite0_failed,
+ {require,Reason}}}}),
{skip,{require_failed_in_suite0,Reason}};
{error,Reason} ->
{auto_skip,{require_failed,Reason}};
@@ -656,7 +667,20 @@ end_tc(Mod,Func,TCPid,Result,Args,Return) ->
end,
ct_util:reset_silent_connections(),
-
+
+ %% reset the curr_tc state, or delete this TC from the list of
+ %% executing cases (if in a parallel group)
+ ClearCurrTC = fun(Running = [_,_|_]) ->
+ lists:delete({Mod,Func},Running);
+ ({_,{suite0_failed,_}}) ->
+ undefined;
+ ([{_,CurrTC}]) when CurrTC == Func ->
+ undefined;
+ (undefined) ->
+ undefined
+ end,
+ ct_util:update_testdata(curr_tc,ClearCurrTC),
+
case FinalResult of
{skip,{sequence_failed,_,_}} ->
%% ct_logs:init_tc is never called for a skipped test case
@@ -1657,5 +1681,5 @@ format_comment(Comment) ->
%%%-----------------------------------------------------------------
%%% @spec get_html_wrapper(TestName, PrintLabel, Cwd) -> Header
-get_html_wrapper(TestName, PrintLabel, Cwd) ->
- ct_logs:get_ts_html_wrapper(TestName, PrintLabel, Cwd).
+get_html_wrapper(TestName, PrintLabel, Cwd, TableCols) ->
+ ct_logs:get_ts_html_wrapper(TestName, PrintLabel, Cwd, TableCols).
diff --git a/lib/common_test/src/ct_gen_conn.erl b/lib/common_test/src/ct_gen_conn.erl
index 5df9127725..1f01d84601 100644
--- a/lib/common_test/src/ct_gen_conn.erl
+++ b/lib/common_test/src/ct_gen_conn.erl
@@ -121,7 +121,7 @@ start(Name,Address,InitData,CallbackMod) ->
%%%
%%% @doc Close the connection and stop the process managing it.
stop(Pid) ->
- call(Pid,stop).
+ call(Pid,stop,5000).
%%%-----------------------------------------------------------------
%%% @spec log(Heading,Format,Args) -> ok
@@ -251,9 +251,10 @@ check_opts([{old,Bool}|T],Opts) ->
check_opts([],Opts) ->
Opts.
-call(Pid,Msg) ->
- call(Pid,Msg,infinity).
-call(Pid,Msg,Timeout) ->
+call(Pid, Msg) ->
+ call(Pid, Msg, infinity).
+
+call(Pid, Msg, Timeout) ->
MRef = erlang:monitor(process,Pid),
Ref = make_ref(),
Pid ! {Msg,{self(),Ref}},
@@ -270,7 +271,11 @@ call(Pid,Msg,Timeout) ->
{error,{process_down,Pid,Reason}}
after Timeout ->
erlang:demonitor(MRef, [flush]),
- exit(timeout)
+ log("ct_gen_conn",
+ "Connection process ~p not responding. Killing now!",
+ [Pid]),
+ exit(Pid, kill),
+ {error,{process_down,Pid,forced_termination}}
end.
return({To,Ref},Result) ->
diff --git a/lib/common_test/src/ct_hooks.erl b/lib/common_test/src/ct_hooks.erl
index 98b74665de..d0432b604d 100644
--- a/lib/common_test/src/ct_hooks.erl
+++ b/lib/common_test/src/ct_hooks.erl
@@ -192,12 +192,12 @@ call([{Hook, call_id, NextFun} | Rest], Config, Meta, Hooks) ->
case lists:keyfind(NewId, #ct_hook_config.id, Hooks) of
false when NextFun =:= undefined ->
{Hooks ++ [NewHook],
- [{NewId, call_init} | Rest]};
+ Rest ++ [{NewId, call_init}]};
ExistingHook when is_tuple(ExistingHook) ->
{Hooks, Rest};
_ ->
{Hooks ++ [NewHook],
- [{NewId, call_init}, {NewId,NextFun} | Rest]}
+ Rest ++ [{NewId, call_init}, {NewId,NextFun}]}
end,
call(resort(NewRest,NewHooks,Meta), Config, Meta, NewHooks)
catch Error:Reason ->
diff --git a/lib/common_test/src/ct_logs.erl b/lib/common_test/src/ct_logs.erl
index 8359dcee98..0b7a8bb075 100644
--- a/lib/common_test/src/ct_logs.erl
+++ b/lib/common_test/src/ct_logs.erl
@@ -28,23 +28,25 @@
-module(ct_logs).
--export([init/1,close/2,init_tc/1,end_tc/1]).
--export([get_log_dir/0,get_log_dir/1]).
--export([log/3,start_log/1,cont_log/2,end_log/0]).
--export([set_stylesheet/2,clear_stylesheet/1]).
--export([add_external_logs/1,add_link/3]).
+-export([init/2, close/2, init_tc/1, end_tc/1]).
+-export([get_log_dir/0, get_log_dir/1]).
+-export([log/3, start_log/1, cont_log/2, end_log/0]).
+-export([set_stylesheet/2, clear_stylesheet/1]).
+-export([add_external_logs/1, add_link/3]).
-export([make_last_run_index/0]).
-export([make_all_suites_index/1,make_all_runs_index/1]).
--export([get_ts_html_wrapper/3]).
--export([xhtml/2, locate_default_css_file/0, make_relative/1]).
+-export([get_ts_html_wrapper/4]).
+-export([xhtml/2, locate_priv_file/1, make_relative/1]).
+-export([insert_javascript/1]).
%% Logging stuff directly from testcase
--export([tc_log/3,tc_log/4,tc_log_async/3,tc_print/3,tc_pal/3,ct_log/3,
- basic_html/0]).
+-export([tc_log/3, tc_log/4, tc_log_async/3, tc_print/3, tc_print/4,
+ tc_pal/3, tc_pal/4, ct_log/3, basic_html/0]).
%% Simulate logger process for use without ct environment running
-export([simulate/0]).
+-include("ct.hrl").
-include("ct_event.hrl").
-include("ct_util.hrl").
-include_lib("kernel/include/file.hrl").
@@ -56,7 +58,6 @@
-define(all_runs_name, "all_runs.html").
-define(index_name, "index.html").
-define(totals_name, "totals.info").
--define(css_default, "ct_default.css").
-define(table_color1,"#ADD8E6").
-define(table_color2,"#E4F0FE").
@@ -79,9 +80,9 @@
%%% started. A new directory named ct_run.&lt;timestamp&gt; is created
%%% and all logs are stored under this directory.</p>
%%%
-init(Mode) ->
+init(Mode, Verbosity) ->
Self = self(),
- Pid = spawn_link(fun() -> logger(Self,Mode) end),
+ Pid = spawn_link(fun() -> logger(Self, Mode, Verbosity) end),
MRef = erlang:monitor(process,Pid),
receive
{started,Pid,Result} ->
@@ -240,7 +241,7 @@ end_tc(TCPid) ->
%%% activity it is. <code>Format</code> and <code>Args</code> is the
%%% data to log (as in <code>io:format(Format,Args)</code>).</p>
log(Heading,Format,Args) ->
- cast({log,sync,self(),group_leader(),
+ cast({log,sync,self(),group_leader(),ct_internal,?MAX_IMPORTANCE,
[{int_header(),[log_timestamp(now()),Heading]},
{Format,Args},
{int_footer(),[]}]}),
@@ -262,7 +263,7 @@ log(Heading,Format,Args) ->
%%% @see cont_log/2
%%% @see end_log/0
start_log(Heading) ->
- cast({log,sync,self(),group_leader(),
+ cast({log,sync,self(),group_leader(),ct_internal,?MAX_IMPORTANCE,
[{int_header(),[log_timestamp(now()),Heading]}]}),
ok.
@@ -277,7 +278,8 @@ cont_log([],[]) ->
ok;
cont_log(Format,Args) ->
maybe_log_timestamp(),
- cast({log,sync,self(),group_leader(),[{Format,Args}]}),
+ cast({log,sync,self(),group_leader(),ct_internal,?MAX_IMPORTANCE,
+ [{Format,Args}]}),
ok.
%%%-----------------------------------------------------------------
@@ -288,7 +290,8 @@ cont_log(Format,Args) ->
%%% @see start_log/1
%%% @see cont_log/2
end_log() ->
- cast({log,sync,self(),group_leader(),[{int_footer(), []}]}),
+ cast({log,sync,self(),group_leader(),ct_internal,?MAX_IMPORTANCE,
+ [{int_footer(), []}]}),
ok.
@@ -321,10 +324,16 @@ add_link(Heading,File,Type) ->
[filename:join("log_private",File),Type,File]).
-
%%%-----------------------------------------------------------------
%%% @spec tc_log(Category,Format,Args) -> ok
+%%% @equiv tc_log(Category,?STD_IMPORTANCE,Format,Args)
+tc_log(Category,Format,Args) ->
+ tc_log(Category,?STD_IMPORTANCE,Format,Args).
+
+%%%-----------------------------------------------------------------
+%%% @spec tc_log(Category,Importance,Format,Args) -> ok
%%% Category = atom()
+%%% Importance = integer()
%%% Format = string()
%%% Args = list()
%%%
@@ -333,19 +342,26 @@ add_link(Heading,File,Type) ->
%%% <p>This function is called by <code>ct</code> when logging
%%% stuff directly from a testcase (i.e. not from within the CT
%%% framework).</p>
-tc_log(Category,Format,Args) ->
- tc_log(Category,"User",Format,Args).
+tc_log(Category,Importance,Format,Args) ->
+ tc_log(Category,Importance,"User",Format,Args).
-tc_log(Category,Printer,Format,Args) ->
- cast({log,sync,self(),group_leader(),[{div_header(Category,Printer),[]},
- {Format,Args},
- {div_footer(),[]}]}),
+tc_log(Category,Importance,Printer,Format,Args) ->
+ cast({log,sync,self(),group_leader(),Category,Importance,
+ [{div_header(Category,Printer),[]},
+ {Format,Args},
+ {div_footer(),[]}]}),
ok.
-
%%%-----------------------------------------------------------------
%%% @spec tc_log_async(Category,Format,Args) -> ok
+%%% @equiv tc_log_async(Category,?STD_IMPORTANCE,Format,Args)
+tc_log_async(Category,Format,Args) ->
+ tc_log_async(Category,?STD_IMPORTANCE,Format,Args).
+
+%%%-----------------------------------------------------------------
+%%% @spec tc_log_async(Category,Importance,Format,Args) -> ok
%%% Category = atom()
+%%% Importance = integer()
%%% Format = string()
%%% Args = list()
%%%
@@ -356,40 +372,66 @@ tc_log(Category,Printer,Format,Args) ->
%%% to avoid deadlocks when e.g. the hook that handles SASL printouts
%%% prints to the test case log file at the same time test server
%%% asks ct_logs for an html wrapper.</p>
-tc_log_async(Category,Format,Args) ->
- cast({log,async,self(),group_leader(),[{div_header(Category),[]},
- {Format,Args},
- {div_footer(),[]}]}),
+tc_log_async(Category,Importance,Format,Args) ->
+ cast({log,async,self(),group_leader(),Category,Importance,
+ [{div_header(Category),[]},
+ {Format,Args},
+ {div_footer(),[]}]}),
ok.
+%%%-----------------------------------------------------------------
+%%% @spec tc_print(Category,Format,Args)
+%%% @equiv tc_print(Category,?STD_IMPORTANCE,Format,Args)
+tc_print(Category,Format,Args) ->
+ tc_print(Category,?STD_IMPORTANCE,Format,Args).
%%%-----------------------------------------------------------------
-%%% @spec tc_print(Category,Format,Args) -> ok
+%%% @spec tc_print(Category,Importance,Format,Args) -> ok
%%% Category = atom()
+%%% Importance = integer()
%%% Format = string()
%%% Args = list()
%%%
%%% @doc Console printout from a testcase.
%%%
%%% <p>This function is called by <code>ct</code> when printing
-%%% stuff a testcase on the user console.</p>
-tc_print(Category,Format,Args) ->
- Head = get_heading(Category),
- io:format(user, lists:concat([Head,Format,"\n\n"]), Args),
- ok.
+%%% stuff from a testcase on the user console.</p>
+tc_print(Category,Importance,Format,Args) ->
+ VLvl = case ct_util:get_testdata({verbosity,Category}) of
+ undefined ->
+ ct_util:get_testdata({verbosity,'$unspecified'});
+ {error,bad_invocation} ->
+ ?MAX_VERBOSITY;
+ Val ->
+ Val
+ end,
+ if Importance >= (100-VLvl) ->
+ Head = get_heading(Category),
+ io:format(user, lists:concat([Head,Format,"\n\n"]), Args),
+ ok;
+ true ->
+ ok
+ end.
get_heading(default) ->
- io_lib:format("-----------------------------"
+ io_lib:format("\n-----------------------------"
"-----------------------\n~s\n",
[log_timestamp(now())]);
get_heading(Category) ->
- io_lib:format("-----------------------------"
+ io_lib:format("\n-----------------------------"
"-----------------------\n~s ~w\n",
[log_timestamp(now()),Category]).
%%%-----------------------------------------------------------------
%%% @spec tc_pal(Category,Format,Args) -> ok
+%%% @equiv tc_pal(Category,?STD_IMPORTANCE,Format,Args) -> ok
+tc_pal(Category,Format,Args) ->
+ tc_pal(Category,?STD_IMPORTANCE,Format,Args).
+
+%%%-----------------------------------------------------------------
+%%% @spec tc_pal(Category,Importance,Format,Args) -> ok
%%% Category = atom()
+%%% Importance = integer()
%%% Format = string()
%%% Args = list()
%%%
@@ -398,16 +440,17 @@ get_heading(Category) ->
%%% <p>This function is called by <code>ct</code> when logging
%%% stuff directly from a testcase. The info is written both in the
%%% log and on the console.</p>
-tc_pal(Category,Format,Args) ->
- tc_print(Category,Format,Args),
- cast({log,sync,self(),group_leader(),[{div_header(Category),[]},
- {Format,Args},
- {div_footer(),[]}]}),
+tc_pal(Category,Importance,Format,Args) ->
+ tc_print(Category,Importance,Format,Args),
+ cast({log,sync,self(),group_leader(),Category,Importance,
+ [{div_header(Category),[]},
+ {Format,Args},
+ {div_footer(),[]}]}),
ok.
%%%-----------------------------------------------------------------
-%%% @spec tc_pal(Category,Format,Args) -> ok
+%%% @spec ct_pal(Category,Format,Args) -> ok
%%% Category = atom()
%%% Format = string()
%%% Args = list()
@@ -445,7 +488,7 @@ maybe_log_timestamp() ->
{MS,S,_} ->
ok;
_ ->
- cast({log,sync,self(),group_leader(),
+ cast({log,sync,self(),group_leader(),ct_internal,?MAX_IMPORTANCE,
[{"<i>~s</i>",[log_timestamp({MS,S,US})]}]})
end.
@@ -469,7 +512,7 @@ log_timestamp({MS,S,US}) ->
stylesheet,
async_print_jobs}).
-logger(Parent,Mode) ->
+logger(Parent, Mode, Verbosity) ->
register(?MODULE,self()),
%%! Below is a temporary workaround for the limitation of
@@ -502,26 +545,27 @@ logger(Parent,Mode) ->
%% dir) so logs are independent of Common Test installation
{ok,Cwd} = file:get_cwd(),
CTPath = code:lib_dir(common_test),
- CSSFileSrc = filename:join(filename:join(CTPath, "priv"),
- ?css_default),
- CSSFileDestTop = filename:join(Cwd, ?css_default),
- CSSFileDestRun = filename:join(AbsDir, ?css_default),
- case file:copy(CSSFileSrc, CSSFileDestTop) of
- {error,Reason0} ->
+ PrivFiles = [?css_default,?jquery_script,?tablesorter_script],
+ PrivFilesSrc = [filename:join(filename:join(CTPath, "priv"), F) ||
+ F <- PrivFiles],
+ PrivFilesDestTop = [filename:join(Cwd, F) || F <- PrivFiles],
+ PrivFilesDestRun = [filename:join(AbsDir, F) || F <- PrivFiles],
+ case copy_priv_files(PrivFilesSrc, PrivFilesDestTop) of
+ {error,Src1,Dest1,Reason1} ->
io:format(user, "ERROR! "++
- "CSS file ~p could not be copied to ~p. "++
- "Reason: ~p~n",
- [CSSFileSrc,CSSFileDestTop,Reason0]),
- exit({css_file_error,CSSFileDestTop});
- _ ->
- case file:copy(CSSFileSrc, CSSFileDestRun) of
- {error,Reason1} ->
+ "Priv file ~p could not be copied to ~p. "++
+ "Reason: ~p~n",
+ [Src1,Dest1,Reason1]),
+ exit({priv_file_error,Dest1});
+ ok ->
+ case copy_priv_files(PrivFilesSrc, PrivFilesDestRun) of
+ {error,Src2,Dest2,Reason2} ->
io:format(user, "ERROR! "++
- "CSS file ~p could not be copied to ~p. "++
- "Reason: ~p~n",
- [CSSFileSrc,CSSFileDestRun,Reason1]),
- exit({css_file_error,CSSFileDestRun});
- _ ->
+ "Priv file ~p could not be copied to ~p. "++
+ "Reason: ~p~n",
+ [Src2,Dest2,Reason2]),
+ exit({priv_file_error,Dest2});
+ ok ->
ok
end
end
@@ -541,6 +585,23 @@ logger(Parent,Mode) ->
[log_timestamp(now()),"Common Test Logger started"]),
Parent ! {started,self(),{Time,filename:absname("")}},
set_evmgr_gl(CtLogFd),
+
+ %% save verbosity levels in dictionary for fast lookups
+ io:format(CtLogFd, "\nVERBOSITY LEVELS:\n", []),
+ case proplists:get_value('$unspecified', Verbosity) of
+ undefined -> ok;
+ GenLvl -> io:format(CtLogFd, "~-25s~3w~n",
+ ["general level",GenLvl])
+ end,
+ [begin put({verbosity,Cat},VLvl),
+ if Cat == '$unspecified' ->
+ ok;
+ true ->
+ io:format(CtLogFd, "~-25w~3w~n", [Cat,VLvl])
+ end
+ end || {Cat,VLvl} <- Verbosity],
+ io:nl(CtLogFd),
+
logger_loop(#logger_state{parent=Parent,
log_dir=AbsDir,
start_time=Time,
@@ -549,31 +610,58 @@ logger(Parent,Mode) ->
tc_groupleaders=[],
async_print_jobs=[]}).
+copy_priv_files([SrcF | SrcFs], [DestF | DestFs]) ->
+ case file:copy(SrcF, DestF) of
+ {error,Reason} ->
+ {error,SrcF,DestF,Reason};
+ _ ->
+ copy_priv_files(SrcFs, DestFs)
+ end;
+copy_priv_files([], []) ->
+ ok.
+
logger_loop(State) ->
receive
- {log,SyncOrAsync,Pid,GL,List} ->
- case get_groupleader(Pid, GL, State) of
- {tc_log,TCGL,TCGLs} ->
- case erlang:is_process_alive(TCGL) of
- true ->
- State1 = print_to_log(SyncOrAsync, Pid, TCGL,
- List, State),
- logger_loop(State1#logger_state{tc_groupleaders =
- TCGLs});
- false ->
- %% Group leader is dead, so write to the
- %% CtLog instead
- Fd = State#logger_state.ct_log_fd,
- [begin io:format(Fd,Str,Args),io:nl(Fd) end ||
+ {log,SyncOrAsync,Pid,GL,Category,Importance,List} ->
+ VLvl = case Category of
+ ct_internal ->
+ ?MAX_VERBOSITY;
+ _ ->
+ case get({verbosity,Category}) of
+ undefined -> get({verbosity,'$unspecified'});
+ Val -> Val
+ end
+ end,
+ if Importance >= (100-VLvl) ->
+ case get_groupleader(Pid, GL, State) of
+ {tc_log,TCGL,TCGLs} ->
+ case erlang:is_process_alive(TCGL) of
+ true ->
+ State1 = print_to_log(SyncOrAsync, Pid,
+ TCGL, List, State),
+ logger_loop(State1#logger_state{
+ tc_groupleaders = TCGLs});
+ false ->
+ %% Group leader is dead, so write to the
+ %% CtLog instead
+ Fd = State#logger_state.ct_log_fd,
+ [begin io:format(Fd,Str,Args),
+ io:nl(Fd) end || {Str,Args} <- List],
+ logger_loop(State)
+ end;
+ {ct_log,Fd,TCGLs} ->
+ [begin io:format(Fd,Str,Args),io:nl(Fd) end ||
{Str,Args} <- List],
- logger_loop(State)
+ logger_loop(State#logger_state{
+ tc_groupleaders = TCGLs})
end;
- {ct_log,Fd,TCGLs} ->
- [begin io:format(Fd,Str,Args),io:nl(Fd) end ||
- {Str,Args} <- List],
- logger_loop(State#logger_state{tc_groupleaders = TCGLs})
- end;
+ true ->
+ logger_loop(State)
+ end;
{{init_tc,TCPid,GL,RefreshLog},From} ->
+ %% make sure no IO for this test case from the
+ %% CT logger gets rejected
+ test_server:permit_io(GL, self()),
print_style(GL, State#logger_state.stylesheet),
set_evmgr_gl(GL),
TCGLs = add_tc_gl(TCPid,GL,State),
@@ -675,6 +763,7 @@ print_to_log(async, FromPid, TCGL, List, State) ->
true -> State#logger_state.ct_log_fd
end,
Printer = fun() ->
+ test_server:permit_io(IoProc, self()),
io:format(IoProc, "~s", [lists:foldl(IoFun, [], List)])
end,
case State#logger_state.async_print_jobs of
@@ -780,7 +869,7 @@ set_evmgr_gl(GL) ->
open_ctlog() ->
{ok,Fd} = file:open(?ct_log_name,[write]),
- io:format(Fd, header("Common Test Framework Log"), []),
+ io:format(Fd, header("Common Test Framework Log", {[],[1,2],[]}), []),
case file:consult(ct_run:variables_file_name("../")) of
{ok,Vars} ->
io:format(Fd, config_table(Vars), []);
@@ -1090,14 +1179,14 @@ total_row(Success, Fail, UserSkip, AutoSkip, NotBuilt, All) ->
integer_to_list(UserSkip),integer_to_list(AutoSkip)}
end,
[xhtml("<tr valign=top>\n",
- ["<tr class=\"",odd_or_even(),"\">\n"]),
+ ["</tbody>\n<tfoot>\n<tr class=\"",odd_or_even(),"\">\n"]),
"<td><b>Total</b></td>\n", Label, TimestampCell,
"<td align=right><b>",integer_to_list(Success),"<b></td>\n",
"<td align=right><b>",integer_to_list(Fail),"<b></td>\n",
"<td align=right>",integer_to_list(AllSkip),
" (",UserSkipStr,"/",AutoSkipStr,")</td>\n",
"<td align=right><b>",integer_to_list(NotBuilt),"<b></td>\n",
- AllInfo, "</tr>\n"].
+ AllInfo, "</tr>\n</tfoot>\n"].
not_built(_BaseName,_LogDir,_All,[]) ->
0;
@@ -1154,10 +1243,12 @@ index_header(Label, StartTime) ->
Head =
case Label of
undefined ->
- header("Test Results", format_time(StartTime));
+ header("Test Results", format_time(StartTime),
+ {[],[1],[2,3,4,5]});
_ ->
header("Test Results for '" ++ Label ++ "'",
- format_time(StartTime))
+ format_time(StartTime),
+ {[],[1],[2,3,4,5]})
end,
[Head |
["<center>\n",
@@ -1169,15 +1260,17 @@ index_header(Label, StartTime) ->
"\">COMMON TEST FRAMEWORK LOG</a>\n</div>"]),
xhtml("<br>\n", "<br /><br /><br />\n"),
xhtml(["<table border=\"3\" cellpadding=\"5\" "
- "bgcolor=\"",?table_color3,"\">\n"], "<table>\n"),
+ "bgcolor=\"",?table_color3,"\">\n"],
+ ["<table id=\"",?sortable_table_name,"\">\n",
+ "<thead>\n<tr>\n"]),
"<th><b>Test Name</b></th>\n",
xhtml(["<th><font color=\"",?table_color3,"\">_</font>Ok"
"<font color=\"",?table_color3,"\">_</font></th>\n"],
"<th>Ok</th>\n"),
"<th>Failed</th>\n",
"<th>Skipped", xhtml("<br>", "<br />"), "(User/Auto)</th>\n"
- "<th>Missing", xhtml("<br>", "<br />"), "Suites</th>\n"
- "\n"]].
+ "<th>Missing", xhtml("<br>", "<br />"), "Suites</th>\n",
+ xhtml("", "</tr>\n</thead>\n<tbody>\n")]].
all_suites_index_header() ->
{ok,Cwd} = file:get_cwd(),
@@ -1190,12 +1283,14 @@ all_suites_index_header(IndexDir) ->
AllRunsLink = xhtml(["<a href=\"",?all_runs_name,"\">",AllRuns,"</a>\n"],
["<div id=\"button_holder\" class=\"btn\">\n"
"<a href=\"",?all_runs_name,"\">",AllRuns,"</a>\n</div>"]),
- [header("Test Results") |
+ [header("Test Results", {[3],[1,2,8,9,10],[4,5,6,7]}) |
["<center>\n",
AllRunsLink,
xhtml("<br><br>\n", "<br /><br />\n"),
xhtml(["<table border=\"3\" cellpadding=\"5\" "
- "bgcolor=\"",?table_color2,"\">\n"], "<table>\n"),
+ "bgcolor=\"",?table_color2,"\">\n"],
+ ["<table id=\"",?sortable_table_name,"\">\n",
+ "<thead>\n<tr>\n"]),
"<th>Test Name</th>\n",
"<th>Label</th>\n",
"<th>Test Run Started</th>\n",
@@ -1208,7 +1303,7 @@ all_suites_index_header(IndexDir) ->
"<th>Node</th>\n",
"<th>CT Log</th>\n",
"<th>Old Runs</th>\n",
- "\n"]].
+ xhtml("", "</tr>\n</thead>\n<tbody>\n")]].
all_runs_header() ->
{ok,Cwd} = file:get_cwd(),
@@ -1220,10 +1315,12 @@ all_runs_header() ->
"<a href=\"",?index_name,
"\">TEST INDEX PAGE</a>\n</div>"]),
xhtml("<br>\n", "<br /><br />\n")],
- [header(Title) |
+ [header(Title, {[1],[2,3,5],[4,6,7,8,9,10]}) |
["<center>\n", IxLink,
xhtml(["<table border=\"3\" cellpadding=\"5\" "
- "bgcolor=\"",?table_color1,"\">\n"], "<table>\n"),
+ "bgcolor=\"",?table_color1,"\">\n"],
+ ["<table id=\"",?sortable_table_name,"\">\n",
+ "<thead>\n<tr>\n"]),
"<th><b>History</b></th>\n"
"<th><b>Node</b></th>\n"
"<th><b>Label</b></th>\n"
@@ -1235,23 +1332,29 @@ all_runs_header() ->
"<th>Ok</th>\n"),
"<th>Failed</th>\n"
"<th>Skipped<br>(User/Auto)</th>\n"
- "<th>Missing<br>Suites</th>\n"
- "\n"]].
+ "<th>Missing<br>Suites</th>\n",
+ xhtml("", "</tr>\n</thead>\n<tbody>\n")]].
-header(Title) ->
- header1(Title, "").
-header(Title, SubTitle) ->
- header1(Title, SubTitle).
+header(Title, TableCols) ->
+ header1(Title, "", TableCols).
+header(Title, SubTitle, TableCols) ->
+ header1(Title, SubTitle, TableCols).
-header1(Title, SubTitle) ->
+header1(Title, SubTitle, TableCols) ->
SubTitleHTML = if SubTitle =/= "" ->
["<center>\n",
"<h3>" ++ SubTitle ++ "</h3>\n",
xhtml("</center>\n<br>\n", "</center>\n<br />\n")];
- true -> xhtml("<br>\n", "<br />\n")
+ true -> xhtml("<br>", "<br />")
end,
CSSFile = xhtml(fun() -> "" end,
- fun() -> make_relative(locate_default_css_file()) end),
+ fun() -> make_relative(locate_priv_file(?css_default)) end),
+ JQueryFile =
+ xhtml(fun() -> "" end,
+ fun() -> make_relative(locate_priv_file(?jquery_script)) end),
+ TableSorterFile =
+ xhtml(fun() -> "" end,
+ fun() -> make_relative(locate_priv_file(?tablesorter_script)) end),
[xhtml(["<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 3.2 Final//EN\">\n",
"<html>\n"],
["<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\"\n",
@@ -1262,7 +1365,17 @@ header1(Title, SubTitle) ->
"<title>" ++ Title ++ " " ++ SubTitle ++ "</title>\n",
"<meta http-equiv=\"cache-control\" content=\"no-cache\">\n",
xhtml("",
- ["<link rel=\"stylesheet\" href=\"",CSSFile,"\" type=\"text/css\">"]),
+ ["<link rel=\"stylesheet\" href=\"",CSSFile,"\" type=\"text/css\">\n"]),
+ xhtml("",
+ ["<script type=\"text/javascript\" src=\"",JQueryFile,
+ "\"></script>\n"]),
+ xhtml("",
+ ["<script type=\"text/javascript\" src=\"",TableSorterFile,
+ "\"></script>\n"]),
+ xhtml(fun() -> "" end,
+ fun() -> insert_javascript({tablesorter,?sortable_table_name,
+ TableCols})
+ end),
"</head>\n",
body_tag(),
"<center>\n",
@@ -1274,6 +1387,10 @@ index_footer() ->
["</table>\n"
"</center>\n" | footer()].
+all_runs_index_footer() ->
+ ["</tbody>\n</table>\n"
+ "</center>\n" | footer()].
+
footer() ->
["<center>\n",
xhtml("<br><br>\n<hr>\n", "<br /><br />\n"),
@@ -1285,7 +1402,8 @@ footer() ->
xhtml("<br>\n", "<br />\n"),
xhtml("</font></p>\n", "</div>\n"),
"</center>\n"
- "</body>\n"].
+ "</body>\n"
+ "</html>\n"].
body_tag() ->
@@ -1301,7 +1419,7 @@ current_time() ->
format_time({{Y, Mon, D}, {H, Min, S}}) ->
Weekday = weekday(calendar:day_of_the_week(Y, Mon, D)),
- lists:flatten(io_lib:format("~s ~s ~p ~w ~2.2.0w:~2.2.0w:~2.2.0w",
+ lists:flatten(io_lib:format("~s ~s ~2.2.0w ~w ~2.2.0w:~2.2.0w:~2.2.0w",
[Weekday, month(Mon), D, Y, H, Min, S])).
weekday(1) -> "Mon";
@@ -1427,8 +1545,12 @@ config_table_header() ->
[
xhtml(["<h2>Configuration</h2>\n"
"<table border=\"3\" cellpadding=\"5\" bgcolor=\"",?table_color1,"\"\n"],
- "<h4>CONFIGURATION</h4>\n<table>\n"),
- "<tr><th>Key</th><th>Value</th></tr>\n"].
+ ["<h4>CONFIGURATION</h4>\n",
+ "<table id=\"",?sortable_table_name,"\">\n",
+ "<thead>\n"]),
+ "<tr><th>Key</th><th>Value</th></tr>\n",
+ xhtml("", "</thead>\n<tbody>\n")
+ ].
config_table1([{Key,Value}|Vars]) ->
[xhtml(["<tr><td>", atom_to_list(Key), "</td>\n",
@@ -1438,7 +1560,7 @@ config_table1([{Key,Value}|Vars]) ->
"<td>", io_lib:format("~p",[Value]), "</td>\n</tr>\n"]) |
config_table1(Vars)];
config_table1([]) ->
- ["</table>\n"].
+ ["</tbody>\n</table>\n"].
make_all_runs_index(When) ->
@@ -1452,7 +1574,8 @@ make_all_runs_index(When) ->
DirsSorted = (catch sort_all_runs(Dirs)),
Header = all_runs_header(),
Index = [runentry(Dir) || Dir <- DirsSorted],
- Result = file:write_file(AbsName,Header++Index++index_footer()),
+ Result = file:write_file(AbsName,Header++Index++
+ all_runs_index_footer()),
if When == start -> ok;
true -> io:put_chars("done\n")
end,
@@ -1991,7 +2114,7 @@ simulate() ->
simulate_logger_loop() ->
receive
- {log,_,_,_,List} ->
+ {log,_,_,_,_,_,List} ->
S = [[io_lib:format(Str,Args),io_lib:nl()] || {Str,Args} <- List],
io:format("~s",[S]),
simulate_logger_loop();
@@ -2088,34 +2211,34 @@ basic_html() ->
end.
%%%-----------------------------------------------------------------
-%%% @spec locate_default_css_file() -> CSSFile
+%%% @spec locate_priv_file(FileName) -> PrivFile
%%%
%%% @doc
%%%
-locate_default_css_file() ->
+locate_priv_file(FileName) ->
{ok,CWD} = file:get_cwd(),
- CSSFileInCwd = filename:join(CWD, ?css_default),
- case filelib:is_file(CSSFileInCwd) of
+ PrivFileInCwd = filename:join(CWD, FileName),
+ case filelib:is_file(PrivFileInCwd) of
true ->
- CSSFileInCwd;
+ PrivFileInCwd;
false ->
- CSSResultFile =
+ PrivResultFile =
case {whereis(?MODULE),self()} of
{Self,Self} ->
%% executed on the ct_logs process
- filename:join(get(ct_run_dir), ?css_default);
+ filename:join(get(ct_run_dir), FileName);
_ ->
%% executed on other process than ct_logs
{ok,RunDir} = get_log_dir(true),
- filename:join(RunDir, ?css_default)
+ filename:join(RunDir, FileName)
end,
- case filelib:is_file(CSSResultFile) of
+ case filelib:is_file(PrivResultFile) of
true ->
- CSSResultFile;
+ PrivResultFile;
false ->
%% last resort, try use css file in CT installation
CTPath = code:lib_dir(common_test),
- filename:join(filename:join(CTPath, "priv"), ?css_default)
+ filename:join(filename:join(CTPath, "priv"), FileName)
end
end.
@@ -2154,7 +2277,7 @@ make_relative1(DirTs, CwdTs) ->
%%%
%%% @doc
%%%
-get_ts_html_wrapper(TestName, PrintLabel, Cwd) ->
+get_ts_html_wrapper(TestName, PrintLabel, Cwd, TableCols) ->
TestName1 = if is_list(TestName) ->
lists:flatten(TestName);
true ->
@@ -2214,17 +2337,36 @@ get_ts_html_wrapper(TestName, PrintLabel, Cwd) ->
"Open Telecom Platform</a><br />\n",
"Updated: <!date>", current_time(), "<!/date>",
"<br />\n</div>\n"],
- CSSFile = xhtml(fun() -> "" end,
- fun() -> make_relative(locate_default_css_file(), Cwd) end),
+ CSSFile =
+ xhtml(fun() -> "" end,
+ fun() -> make_relative(locate_priv_file(?css_default),
+ Cwd)
+ end),
+ JQueryFile =
+ xhtml(fun() -> "" end,
+ fun() -> make_relative(locate_priv_file(?jquery_script),
+ Cwd)
+ end),
+ TableSorterFile =
+ xhtml(fun() -> "" end,
+ fun() -> make_relative(locate_priv_file(?tablesorter_script),
+ Cwd)
+ end),
+ TableSorterScript =
+ xhtml(fun() -> "" end,
+ fun() -> insert_javascript({tablesorter,
+ ?sortable_table_name,
+ TableCols}) end),
{xhtml,
["<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\"\n",
"\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n",
"<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\" lang=\"en\">\n",
"<head>\n<title>", TestName1, "</title>\n",
"<meta http-equiv=\"cache-control\" content=\"no-cache\">\n",
- "<link rel=\"stylesheet\" href=\"", CSSFile, "\" type=\"text/css\">",
- "</head>\n","<body>\n",
- LabelStr, "\n"],
+ "<link rel=\"stylesheet\" href=\"", CSSFile, "\" type=\"text/css\">\n",
+ "<script type=\"text/javascript\" src=\"", JQueryFile, "\"></script>\n",
+ "<script type=\"text/javascript\" src=\"", TableSorterFile, "\"></script>\n"] ++
+ TableSorterScript ++ ["</head>\n","<body>\n", LabelStr, "\n"],
["<center>\n<br /><hr /><p>\n",
"<a href=\"", AllRuns,
"\">Test run history\n</a> | ",
@@ -2232,3 +2374,89 @@ get_ts_html_wrapper(TestName, PrintLabel, Cwd) ->
"\">Top level test index\n</a>\n</p>\n",
Copyright,"</center>\n</body>\n</html>\n"]}
end.
+
+insert_javascript({tablesorter,_TableName,undefined}) ->
+ [];
+
+insert_javascript({tablesorter,TableName,
+ {DateCols,TextCols,ValCols}}) ->
+ Headers =
+ lists:flatten(
+ lists:sort(
+ lists:flatmap(fun({Sorter,Cols}) ->
+ [lists:flatten(
+ io_lib:format(" ~w: "
+ "{ sorter: '~s' },\n",
+ [Col-1,Sorter])) || Col<-Cols]
+ end, [{"CTDateSorter",DateCols},
+ {"CTTextSorter",TextCols},
+ {"CTValSorter",ValCols}]))),
+ Headers1 = string:substr(Headers, 1, length(Headers)-2),
+
+ ["<script type=\"text/javascript\">\n",
+ "// Parser for date format, e.g: Wed Jul 4 2012 11:24:15\n",
+ "var monthNames = {};\n",
+ "monthNames[\"Jan\"] = \"01\"; monthNames[\"Feb\"] = \"02\";\n",
+ "monthNames[\"Mar\"] = \"03\"; monthNames[\"Apr\"] = \"04\";\n",
+ "monthNames[\"May\"] = \"05\"; monthNames[\"Jun\"] = \"06\";\n",
+ "monthNames[\"Jul\"] = \"07\"; monthNames[\"Aug\"] = \"08\";\n",
+ "monthNames[\"Sep\"] = \"09\"; monthNames[\"Oct\"] = \"10\";\n",
+ "monthNames[\"Nov\"] = \"11\"; monthNames[\"Dec\"] = \"12\";\n",
+ "$.tablesorter.addParser({\n",
+ " id: 'CTDateSorter',\n",
+ " is: function(s) {\n",
+ " return false; },\n",
+ " format: function(s) {\n",
+ %% place empty cells, "-" and "?" at the bottom
+ " if (s.length < 2) return 999999999;\n",
+ " else {\n",
+ %% match out each date element
+ " var date = s.match(/(\\w{3})\\s(\\w{3})\\s(\\d{2})\\s(\\d{4})\\s(\\d{2}):(\\d{2}):(\\d{2})/);\n",
+ " var y = date[4]; var mo = monthNames[date[2]]; var d = String(date[3]);\n",
+ " var h = String(date[5]); var mi = String(date[6]); var sec = String(date[7]);\n",
+ " return (parseInt('' + y + mo + d + h + mi + sec)); }},\n",
+ " type: 'numeric' });\n",
+
+ "// Parser for general text format\n",
+ "$.tablesorter.addParser({\n",
+ " id: 'CTTextSorter',\n",
+ " is: function(s) {\n",
+ " return false; },\n",
+ " format: function(s) {\n",
+ %% place empty cells, "?" and "-" at the bottom
+ " if (s.length < 1) return 'zzzzzzzz';\n",
+ " else if (s == \"?\") return 'zzzzzzz';\n",
+ " else if (s == \"-\") return 'zzzzzz';\n",
+ " else if (s == \"FAILED\") return 'A';\n",
+ " else if (s == \"SKIPPED\") return 'B';\n",
+ " else if (s == \"OK\") return 'C';\n",
+ " else return '' + s; },\n",
+ " type: 'text' });\n",
+
+ "// Parser for numerical values\n",
+ "$.tablesorter.addParser({\n",
+ " id: 'CTValSorter',\n",
+ " is: function(s) {\n",
+ " return false; },\n",
+ " format: function(s) {\n"
+ %% place empty cells and "?" at the bottom
+ " if (s.length < 1) return '-2';\n",
+ " else if (s == \"?\") return '-1';\n",
+ %% look for skip value, eg "3 (2/1)"
+ " else if ((s.search(/(\\d{1,})\\s/)) >= 0) {\n",
+ " var num = s.match(/(\\d{1,})\\s/);\n",
+ %% return only the total skip value for sorting
+ " return (parseInt('' + num[1])); }\n",
+ " else if ((s.search(/(\\d{1,})\\.(\\d{3})s/)) >= 0) {\n",
+ " var num = s.match(/(\\d{1,})\\.(\\d{3})/);\n",
+ " if (num[1] == \"0\") return (parseInt('' + num[2]));\n",
+ " else return (parseInt('' + num[1] + num[2])); }\n",
+ " else return '' + s; },\n",
+ " type: 'numeric' });\n",
+
+ "$(document).ready(function() {\n",
+ " $(\"#",TableName,"\").tablesorter({\n",
+ " headers: { \n", Headers1, "\n }\n });\n",
+ " $(\"#",TableName,"\").trigger(\"update\");\n",
+ " $(\"#",TableName,"\").trigger(\"appendCache\");\n",
+ "});\n</script>\n"].
diff --git a/lib/common_test/src/ct_master_logs.erl b/lib/common_test/src/ct_master_logs.erl
index 2a951bc5cf..9e61d5b16f 100644
--- a/lib/common_test/src/ct_master_logs.erl
+++ b/lib/common_test/src/ct_master_logs.erl
@@ -26,6 +26,8 @@
-export([start/2, make_all_runs_index/0, log/3, nodedir/2,
stop/0]).
+-include("ct_util.hrl").
+
-record(state, {log_fd, start_time, logdir, rundir,
nodedir_ix_fd, nodes, nodedirs=[]}).
@@ -33,7 +35,6 @@
-define(all_runs_name, "master_runs.html").
-define(nodedir_index_name, "index.html").
-define(details_file_name,"details.info").
--define(css_default, "ct_default.css").
-define(table_color,"lightblue").
%%%--------------------------------------------------------------------
@@ -95,29 +96,30 @@ init(Parent,LogDir,Nodes) ->
put(basic_html, true);
BasicHtml ->
put(basic_html, BasicHtml),
- %% copy stylesheet to log dir (both top dir and test run
+ %% copy priv files to log dir (both top dir and test run
%% dir) so logs are independent of Common Test installation
CTPath = code:lib_dir(common_test),
- CSSFileSrc = filename:join(filename:join(CTPath, "priv"),
- ?css_default),
- CSSFileDestTop = filename:join(LogDir, ?css_default),
- CSSFileDestRun = filename:join(RunDirAbs, ?css_default),
- case file:copy(CSSFileSrc, CSSFileDestTop) of
- {error,Reason0} ->
+ PrivFiles = [?css_default,?jquery_script,?tablesorter_script],
+ PrivFilesSrc = [filename:join(filename:join(CTPath, "priv"), F) ||
+ F <- PrivFiles],
+ PrivFilesDestTop = [filename:join(LogDir, F) || F <- PrivFiles],
+ PrivFilesDestRun = [filename:join(RunDirAbs, F) || F <- PrivFiles],
+ case copy_priv_files(PrivFilesSrc, PrivFilesDestTop) of
+ {error,Src1,Dest1,Reason1} ->
io:format(user, "ERROR! "++
- "CSS file ~p could not be copied to ~p. "++
+ "Priv file ~p could not be copied to ~p. "++
"Reason: ~p~n",
- [CSSFileSrc,CSSFileDestTop,Reason0]),
- exit({css_file_error,CSSFileDestTop});
- _ ->
- case file:copy(CSSFileSrc, CSSFileDestRun) of
- {error,Reason1} ->
+ [Src1,Dest1,Reason1]),
+ exit({priv_file_error,Dest1});
+ ok ->
+ case copy_priv_files(PrivFilesSrc, PrivFilesDestRun) of
+ {error,Src2,Dest2,Reason2} ->
io:format(user, "ERROR! "++
- "CSS file ~p could not be copied to ~p. "++
+ "Priv file ~p could not be copied to ~p. "++
"Reason: ~p~n",
- [CSSFileSrc,CSSFileDestRun,Reason1]),
- exit({css_file_error,CSSFileDestRun});
- _ ->
+ [Src2,Dest2,Reason2]),
+ exit({priv_file_error,Dest2});
+ ok ->
ok
end
end
@@ -146,6 +148,16 @@ init(Parent,LogDir,Nodes) ->
{N,""}
end,Nodes)}).
+copy_priv_files([SrcF | SrcFs], [DestF | DestFs]) ->
+ case file:copy(SrcF, DestF) of
+ {error,Reason} ->
+ {error,SrcF,DestF,Reason};
+ _ ->
+ copy_priv_files(SrcFs, DestFs)
+ end;
+copy_priv_files([], []) ->
+ ok.
+
loop(State) ->
receive
{log,_From,List} ->
@@ -190,7 +202,7 @@ loop(State) ->
open_ct_master_log(Dir) ->
FullName = filename:join(Dir,?ct_master_log_name),
{ok,Fd} = file:open(FullName,[write]),
- io:format(Fd,header("Common Test Master Log"),[]),
+ io:format(Fd,header("Common Test Master Log", {[],[1,2],[]}),[]),
%% maybe add config info here later
io:format(Fd, config_table([]), []),
io:format(Fd,
@@ -216,11 +228,14 @@ config_table(Vars) ->
config_table_header() ->
["<h2>Configuration</h2>\n",
xhtml(["<table border=\"3\" cellpadding=\"5\" "
- "bgcolor=\"",?table_color,"\"\n"], "<table>\n"),
- "<tr><th>Key</th><th>Value</th></tr>\n"].
+ "bgcolor=\"",?table_color,"\"\n"],
+ ["<table id=\"",?sortable_table_name,"\">\n",
+ "<thead>\n"]),
+ "<tr><th>Key</th><th>Value</th></tr>\n",
+ xhtml("", "</thead>\n<tbody>\n")].
config_table1([]) ->
- ["</table>\n"].
+ ["</tbody>\n</table>\n"].
int_header() ->
"<div class=\"ct_internal\"><b>*** CT MASTER ~s *** ~s</b>".
@@ -250,14 +265,16 @@ close_nodedir_index(Fd) ->
file:close(Fd).
nodedir_index_header(StartTime) ->
- [header("Log Files " ++ format_time(StartTime)) |
+ [header("Log Files " ++ format_time(StartTime), {[],[1,2],[]}) |
["<center>\n",
"<p><a href=\"",?ct_master_log_name,"\">Common Test Master Log</a></p>",
xhtml(["<table border=\"3\" cellpadding=\"5\" "
- "bgcolor=\"",?table_color,"\">\n"], "<table>\n"),
+ "bgcolor=\"",?table_color,"\">\n"],
+ ["<table id=\"",?sortable_table_name,"\">\n",
+ "<thead>\n<tr>\n"]),
"<th><b>Node</b></th>\n",
"<th><b>Log</b></th>\n",
- "\n"]].
+ xhtml("", "</tr>\n</thead>\n<tbody>\n")]].
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%% All Run Index functions %%%
@@ -315,14 +332,16 @@ runentry(Dir) ->
"</tr>\n"].
all_runs_header() ->
- [header("Master Test Runs") |
+ [header("Master Test Runs", {[1],[2,3],[]}) |
["<center>\n",
xhtml(["<table border=\"3\" cellpadding=\"5\" "
- "bgcolor=\"",?table_color,"\">\n"], "<table>\n"),
+ "bgcolor=\"",?table_color,"\">\n"],
+ ["<table id=\"",?sortable_table_name,"\">\n",
+ "<thead>\n<tr>\n"]),
"<th><b>History</b></th>\n"
"<th><b>Master Host</b></th>\n"
- "<th><b>Test Nodes</b></th>\n"
- "\n"]].
+ "<th><b>Test Nodes</b></th>\n",
+ xhtml("", "</tr></thead>\n<tbody>\n")]].
timestamp(Dir) ->
[S,Min,H,D,M,Y|_] = lists:reverse(string:tokens(Dir,".-_")),
@@ -346,9 +365,16 @@ read_details_file(Dir) ->
%%% Internal functions
%%%--------------------------------------------------------------------
-header(Title) ->
+header(Title, TableCols) ->
CSSFile = xhtml(fun() -> "" end,
- fun() -> make_relative(locate_default_css_file()) end),
+ fun() -> make_relative(locate_priv_file(?css_default)) end),
+ JQueryFile =
+ xhtml(fun() -> "" end,
+ fun() -> make_relative(locate_priv_file(?jquery_script)) end),
+ TableSorterFile =
+ xhtml(fun() -> "" end,
+ fun() -> make_relative(locate_priv_file(?tablesorter_script)) end),
+
[xhtml(["<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 3.2 Final//EN\">\n",
"<html>\n"],
["<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\"\n",
@@ -360,6 +386,16 @@ header(Title) ->
"<meta http-equiv=\"cache-control\" content=\"no-cache\">\n",
xhtml("",
["<link rel=\"stylesheet\" href=\"",CSSFile,"\" type=\"text/css\">"]),
+ xhtml("",
+ ["<script type=\"text/javascript\" src=\"",JQueryFile,
+ "\"></script>\n"]),
+ xhtml("",
+ ["<script type=\"text/javascript\" src=\"",TableSorterFile,
+ "\"></script>\n"]),
+ xhtml(fun() -> "" end,
+ fun() -> ct_logs:insert_javascript({tablesorter,
+ ?sortable_table_name,
+ TableCols}) end),
"</head>\n",
body_tag(),
"<center>\n",
@@ -367,7 +403,7 @@ header(Title) ->
"</center>\n"].
index_footer() ->
- ["</table>\n"
+ ["</tbody>\n</table>\n"
"</center>\n" | footer()].
footer() ->
@@ -393,7 +429,7 @@ current_time() ->
format_time({{Y, Mon, D}, {H, Min, S}}) ->
Weekday = weekday(calendar:day_of_the_week(Y, Mon, D)),
- lists:flatten(io_lib:format("~s ~s ~p ~w ~2.2.0w:~2.2.0w:~2.2.0w",
+ lists:flatten(io_lib:format("~s ~s ~2.2.0w ~w ~2.2.0w:~2.2.0w:~2.2.0w",
[Weekday, month(Mon), D, Y, H, Min, S])).
weekday(1) -> "Mon";
@@ -446,8 +482,8 @@ basic_html() ->
xhtml(HTML, XHTML) ->
ct_logs:xhtml(HTML, XHTML).
-locate_default_css_file() ->
- ct_logs:locate_default_css_file().
+locate_priv_file(File) ->
+ ct_logs:locate_priv_file(File).
make_relative(Dir) ->
ct_logs:make_relative(Dir).
diff --git a/lib/common_test/src/ct_netconfc.erl b/lib/common_test/src/ct_netconfc.erl
index d9c4a962dc..5f8bae89fa 100644
--- a/lib/common_test/src/ct_netconfc.erl
+++ b/lib/common_test/src/ct_netconfc.erl
@@ -1074,12 +1074,18 @@ handle_msg({get_event_streams=Op,Streams,Timeout}, From, State) ->
handle_msg({ssh_cm, _CM, {data, _Ch, _Type, Data}}, State) ->
handle_data(Data, State);
-handle_msg({ssh_cm, _CM, {closed,_Ch}}, State) ->
- %% This will happen if the server terminates the connection, as in
- %% kill-session (or if ssh:close is called from somewhere
+handle_msg({ssh_cm, _CM, _SshCloseMsg}, State) ->
+ %% _SshCloseMsg can probably be one of
+ %% {eof,Ch}
+ %% {exit_status,Ch,Status}
+ %% {exit_signal,Ch,ExitSignal,ErrorMsg,LanguageString}
+ %% {signal,Ch,Signal}
+
+ %% This might e.g. happen if the server terminates the connection,
+ %% as in kill-session (or if ssh:close is called from somewhere
%% unexpected).
- %%! Log this?? - i.e. as server closing the connection
+ %%! Log this??
%%! Currently the log will say that the client closed the
%%! connection - due to terminate/2
diff --git a/lib/common_test/src/ct_repeat.erl b/lib/common_test/src/ct_repeat.erl
index 8ecd82f771..a47309c6ee 100644
--- a/lib/common_test/src/ct_repeat.erl
+++ b/lib/common_test/src/ct_repeat.erl
@@ -41,72 +41,86 @@ loop_test(If,Args) when is_list(Args) ->
case get_loop_info(Args) of
no_loop ->
false;
- {error,E} ->
+ E = {error,_} ->
io:format("Common Test error: ~p\n\n",[E]),
file:set_cwd(Cwd),
E;
{repeat,N} ->
io:format("\nCommon Test: Will repeat tests ~w times.\n\n",[N]),
Args1 = [{loop_info,[{repeat,1,N}]} | Args],
- loop(If,repeat,0,N,undefined,Args1,undefined),
- file:set_cwd(Cwd);
+ Result = loop(If,repeat,0,N,undefined,Args1,undefined,[]),
+ file:set_cwd(Cwd),
+ Result;
{stop_time,StopTime} ->
- case remaining_time(StopTime) of
- 0 ->
- io:format("\nCommon Test: No time left to run tests.\n\n",[]),
- ok;
- Secs ->
- io:format("\nCommon Test: Will repeat tests for ~s.\n\n",
- [ts(Secs)]),
- TPid =
- case lists:keymember(force_stop,1,Args) of
- true ->
- CtrlPid = self(),
- spawn(fun() -> stop_after(CtrlPid,Secs) end);
- false ->
- undefined
- end,
- Args1 = [{loop_info,[{stop_time,Secs,StopTime,1}]} | Args],
- loop(If,stop_time,0,Secs,StopTime,Args1,TPid)
- end,
- file:set_cwd(Cwd)
+ Result =
+ case remaining_time(StopTime) of
+ 0 ->
+ io:format("\nCommon Test: "
+ "No time left to run tests.\n\n",[]),
+ {error,not_enough_time};
+ Secs ->
+ io:format("\nCommon Test: "
+ "Will repeat tests for ~s.\n\n",[ts(Secs)]),
+ TPid =
+ case lists:keymember(force_stop,1,Args) of
+ true ->
+ CtrlPid = self(),
+ spawn(fun() -> stop_after(CtrlPid,Secs) end);
+ false ->
+ undefined
+ end,
+ Args1 = [{loop_info,[{stop_time,Secs,StopTime,1}]} | Args],
+ loop(If,stop_time,0,Secs,StopTime,Args1,TPid,[])
+ end,
+ file:set_cwd(Cwd),
+ Result
end.
-loop(_,repeat,N,N,_,_Args,_) ->
- ok;
+loop(_,repeat,N,N,_,_Args,_,AccResult) ->
+ lists:reverse(AccResult);
-loop(If,Type,N,Data0,Data1,Args,TPid) ->
+loop(If,Type,N,Data0,Data1,Args,TPid,AccResult) ->
Pid = spawn_tester(If,self(),Args),
receive
{'EXIT',Pid,Reason} ->
- io:format("Test run crashed! This could be an internal error "
- "- please report!\n\n"
- "~p\n\n",[Reason]),
- cancel(TPid),
- {error,Reason};
+ case Reason of
+ {user_error,What} ->
+ io:format("\nTest run failed!\nReason: ~p\n\n\n", [What]),
+ cancel(TPid),
+ {error,What};
+ _ ->
+ io:format("Test run crashed! This could be an internal error "
+ "- please report!\n\n"
+ "~p\n\n\n",[Reason]),
+ cancel(TPid),
+ {error,Reason}
+ end;
{Pid,{error,Reason}} ->
- io:format("\nTest run failed!\nReason: ~p\n\n",[Reason]),
+ io:format("\nTest run failed!\nReason: ~p\n\n\n",[Reason]),
cancel(TPid),
{error,Reason};
{Pid,Result} ->
if Type == repeat ->
- io:format("\nTest run ~w(~w) complete.\n\n",[N+1,Data0]),
+ io:format("\nTest run ~w(~w) complete.\n\n\n",[N+1,Data0]),
lists:keydelete(loop_info,1,Args),
Args1 = [{loop_info,[{repeat,N+2,Data0}]} | Args],
- loop(If,repeat,N+1,Data0,Data1,Args1,TPid);
+ loop(If,repeat,N+1,Data0,Data1,Args1,TPid,[Result|AccResult]);
Type == stop_time ->
case remaining_time(Data1) of
0 ->
- io:format("\nTest time (~s) has run out.\n\n",[ts(Data0)]),
+ io:format("\nTest time (~s) has run out.\n\n\n",
+ [ts(Data0)]),
cancel(TPid),
- Result;
+ lists:reverse([Result|AccResult]);
Secs ->
io:format("\n~s of test time remaining, "
- "starting run #~w...\n\n",[ts(Secs),N+2]),
+ "starting run #~w...\n\n\n",
+ [ts(Secs),N+2]),
lists:keydelete(loop_info,1,Args),
ST = {stop_time,Data0,Data1,N+2},
Args1 = [{loop_info,[ST]} | Args],
- loop(If,stop_time,N+1,Data0,Data1,Args1,TPid)
+ loop(If,stop_time,N+1,Data0,Data1,Args1,TPid,
+ [Result|AccResult])
end
end
end.
diff --git a/lib/common_test/src/ct_run.erl b/lib/common_test/src/ct_run.erl
index 46aec04ec1..a202ca15e2 100644
--- a/lib/common_test/src/ct_run.erl
+++ b/lib/common_test/src/ct_run.erl
@@ -39,12 +39,20 @@
%% Misc internal functions
-export([variables_file_name/1,script_start1/2,run_test2/1]).
+-include("ct.hrl").
-include("ct_event.hrl").
-include("ct_util.hrl").
-define(abs(Name), filename:absname(Name)).
-define(testdir(Name, Suite), ct_util:get_testdir(Name, Suite)).
+-define(EXIT_STATUS_TEST_SUCCESSFUL, 0).
+-define(EXIT_STATUS_TEST_CASE_FAILED, 1).
+-define(EXIT_STATUS_TEST_RUN_FAILED, 2).
+
+-define(default_verbosity, [{default,?MAX_VERBOSITY},
+ {'$unspecified',?MAX_VERBOSITY}]).
+
-record(opts, {label,
profile,
vts,
@@ -54,11 +62,14 @@
step,
logdir,
logopts = [],
+ basic_html,
+ verbosity = [],
config = [],
event_handlers = [],
ct_hooks = [],
enable_builtin_hooks,
include = [],
+ auto_compile,
silent_connections,
stylesheet,
multiply_timetraps = 1,
@@ -102,7 +113,8 @@ script_start() ->
end, Flags)
end,
%% used for purpose of testing the run_test interface
- io:format(user, "~n-------------------- START ARGS --------------------~n", []),
+ io:format(user, "~n-------------------- START ARGS "
+ "--------------------~n", []),
io:format(user, "--- Init args:~n~p~n", [FlagFilter(Init)]),
io:format(user, "--- CT args:~n~p~n", [FlagFilter(CtArgs)]),
EnvArgs = opts2args(EnvStartOpts),
@@ -110,7 +122,8 @@ script_start() ->
[EnvStartOpts,EnvArgs]),
Merged = merge_arguments(CtArgs ++ EnvArgs),
io:format(user, "--- Merged args:~n~p~n", [FlagFilter(Merged)]),
- io:format(user, "----------------------------------------------------~n~n", []),
+ io:format(user, "-----------------------------------"
+ "-----------------~n~n", []),
Merged;
_ ->
merge_arguments(CtArgs)
@@ -122,46 +135,100 @@ script_start() ->
script_start(Args) ->
Tracing = start_trace(Args),
- Res =
- case ct_repeat:loop_test(script, Args) of
- false ->
- {ok,Cwd} = file:get_cwd(),
- CTVsn =
- case filename:basename(code:lib_dir(common_test)) of
- CTBase when is_list(CTBase) ->
- case string:tokens(CTBase, "-") of
- ["common_test",Vsn] -> " v"++Vsn;
- _ -> ""
- end
- end,
- io:format("~nCommon Test~s starting (cwd is ~s)~n~n", [CTVsn,Cwd]),
- Self = self(),
- Pid = spawn_link(fun() -> script_start1(Self, Args) end),
- receive
- {'EXIT',Pid,Reason} ->
- case Reason of
- {user_error,What} ->
- io:format("\nTest run failed!\nReason: ~p\n\n", [What]),
- {error,What};
- _ ->
- io:format("Test run crashed! This could be an internal error "
- "- please report!\n\n"
- "~p\n\n", [Reason]),
- {error,Reason}
- end;
- {Pid,{error,Reason}} ->
- io:format("\nTest run failed! Reason:\n~p\n\n",[Reason]),
- {error,Reason};
- {Pid,Result} ->
- Result
- end;
- Result ->
- Result
- end,
+ case ct_repeat:loop_test(script, Args) of
+ false ->
+ {ok,Cwd} = file:get_cwd(),
+ CTVsn =
+ case filename:basename(code:lib_dir(common_test)) of
+ CTBase when is_list(CTBase) ->
+ case string:tokens(CTBase, "-") of
+ ["common_test",Vsn] -> " v"++Vsn;
+ _ -> ""
+ end
+ end,
+ io:format("~nCommon Test~s starting (cwd is ~s)~n~n",
+ [CTVsn,Cwd]),
+ Self = self(),
+ Pid = spawn_link(fun() -> script_start1(Self, Args) end),
+ receive
+ {'EXIT',Pid,Reason} ->
+ case Reason of
+ {user_error,What} ->
+ io:format("\nTest run failed!\nReason: ~p\n\n\n",
+ [What]),
+ finish(Tracing, ?EXIT_STATUS_TEST_RUN_FAILED, Args);
+ _ ->
+ io:format("Test run crashed! "
+ "This could be an internal error "
+ "- please report!\n\n"
+ "~p\n\n\n", [Reason]),
+ finish(Tracing, ?EXIT_STATUS_TEST_RUN_FAILED, Args)
+ end;
+ {Pid,{error,Reason}} ->
+ io:format("\nTest run failed! Reason:\n~p\n\n\n",[Reason]),
+ finish(Tracing, ?EXIT_STATUS_TEST_RUN_FAILED, Args);
+ {Pid,Result} ->
+ io:nl(),
+ finish(Tracing, analyze_test_result(Result, Args), Args)
+ end;
+ {error,_LoopReason} ->
+ finish(Tracing, ?EXIT_STATUS_TEST_RUN_FAILED, Args);
+ Result ->
+ io:nl(),
+ finish(Tracing, analyze_test_result(Result, Args), Args)
+ end.
+
+%% analyze the result of one test run, or many (in case of looped test)
+analyze_test_result(ok, _) ->
+ ?EXIT_STATUS_TEST_SUCCESSFUL;
+analyze_test_result({error,_Reason}, _) ->
+ ?EXIT_STATUS_TEST_RUN_FAILED;
+analyze_test_result({_Ok,Failed,{_UserSkipped,AutoSkipped}}, Args) ->
+ if Failed > 0 ->
+ ?EXIT_STATUS_TEST_CASE_FAILED;
+ true ->
+ case AutoSkipped of
+ 0 ->
+ ?EXIT_STATUS_TEST_SUCCESSFUL;
+ _ ->
+ case get_start_opt(exit_status,
+ fun([ExitOpt]) -> ExitOpt end,
+ Args) of
+ undefined ->
+ ?EXIT_STATUS_TEST_CASE_FAILED;
+ "ignore_config" ->
+ ?EXIT_STATUS_TEST_SUCCESSFUL
+ end
+ end
+ end;
+analyze_test_result([Result|Rs], Args) ->
+ case analyze_test_result(Result, Args) of
+ ?EXIT_STATUS_TEST_SUCCESSFUL ->
+ analyze_test_result(Rs, Args);
+ Other ->
+ Other
+ end;
+analyze_test_result([], _) ->
+ ?EXIT_STATUS_TEST_SUCCESSFUL;
+analyze_test_result(Unknown, _) ->
+ io:format("\nTest run failed! Reason:\n~p\n\n\n",[Unknown]),
+ ?EXIT_STATUS_TEST_RUN_FAILED.
+
+finish(Tracing, ExitStatus, Args) ->
stop_trace(Tracing),
timer:sleep(1000),
- io:nl(),
- Res.
+ %% it's possible to tell CT to finish execution with a call
+ %% to a different function than the normal halt/1 BIF
+ %% (meant to be used mainly for reading the CT exit status)
+ case get_start_opt(halt_with,
+ fun([HaltMod,HaltFunc]) -> {list_to_atom(HaltMod),
+ list_to_atom(HaltFunc)} end,
+ Args) of
+ undefined ->
+ halt(ExitStatus);
+ {M,F} ->
+ apply(M, F, [ExitStatus])
+ end.
script_start1(Parent, Args) ->
%% read general start flags
@@ -173,6 +240,7 @@ script_start1(Parent, Args) ->
LogDir = get_start_opt(logdir, fun([LogD]) -> LogD end, Args),
LogOpts = get_start_opt(logopts, fun(Os) -> [list_to_atom(O) || O <- Os] end,
[], Args),
+ Verbosity = verbosity_args2opts(Args),
MultTT = get_start_opt(multiply_timetraps,
fun([MT]) -> list_to_integer(MT) end, 1, Args),
ScaleTT = get_start_opt(scale_timetraps,
@@ -206,7 +274,7 @@ script_start1(Parent, Args) ->
end
end,
%% no_auto_compile + include
- IncludeDirs =
+ {AutoCompile,IncludeDirs} =
case proplists:get_value(no_auto_compile, Args) of
undefined ->
application:set_env(common_test, auto_compile, true),
@@ -222,16 +290,16 @@ script_start1(Parent, Args) ->
case os:getenv("CT_INCLUDE_PATH") of
false ->
application:set_env(common_test, include, InclDirs),
- InclDirs;
+ {undefined,InclDirs};
CtInclPath ->
AllInclDirs =
string:tokens(CtInclPath,[$:,$ ,$,]) ++ InclDirs,
application:set_env(common_test, include, AllInclDirs),
- AllInclDirs
+ {undefined,AllInclDirs}
end;
_ ->
application:set_env(common_test, auto_compile, false),
- []
+ {false,[]}
end,
%% silent connections
SilentConns =
@@ -243,19 +311,24 @@ script_start1(Parent, Args) ->
Stylesheet = get_start_opt(stylesheet,
fun([SS]) -> ?abs(SS) end, Args),
%% basic_html - used by ct_logs
- case proplists:get_value(basic_html, Args) of
- undefined ->
- application:set_env(common_test, basic_html, false);
- _ ->
- application:set_env(common_test, basic_html, true)
- end,
+ BasicHtml = case proplists:get_value(basic_html, Args) of
+ undefined ->
+ application:set_env(common_test, basic_html, false),
+ undefined;
+ _ ->
+ application:set_env(common_test, basic_html, true),
+ true
+ end,
StartOpts = #opts{label = Label, profile = Profile,
vts = Vts, shell = Shell, cover = Cover,
logdir = LogDir, logopts = LogOpts,
+ basic_html = BasicHtml,
+ verbosity = Verbosity,
event_handlers = EvHandlers,
ct_hooks = CTHooks,
enable_builtin_hooks = EnableBuiltinHooks,
+ auto_compile = AutoCompile,
include = IncludeDirs,
silent_connections = SilentConns,
stylesheet = Stylesheet,
@@ -325,9 +398,12 @@ script_start2(StartOpts = #opts{vts = undefined,
AllLogOpts = merge_vals([StartOpts#opts.logopts,
SpecStartOpts#opts.logopts]),
-
- Cover = choose_val(StartOpts#opts.cover,
- SpecStartOpts#opts.cover),
+ AllVerbosity =
+ merge_keyvals([StartOpts#opts.verbosity,
+ SpecStartOpts#opts.verbosity]),
+ Cover =
+ choose_val(StartOpts#opts.cover,
+ SpecStartOpts#opts.cover),
MultTT =
choose_val(StartOpts#opts.multiply_timetraps,
SpecStartOpts#opts.multiply_timetraps),
@@ -352,9 +428,36 @@ script_start2(StartOpts = #opts{vts = undefined,
StartOpts#opts.enable_builtin_hooks,
SpecStartOpts#opts.enable_builtin_hooks),
+ Stylesheet =
+ choose_val(StartOpts#opts.stylesheet,
+ SpecStartOpts#opts.stylesheet),
+
AllInclude = merge_vals([StartOpts#opts.include,
SpecStartOpts#opts.include]),
application:set_env(common_test, include, AllInclude),
+
+ AutoCompile =
+ case choose_val(StartOpts#opts.auto_compile,
+ SpecStartOpts#opts.auto_compile) of
+ undefined ->
+ true;
+ ACBool ->
+ application:set_env(common_test,
+ auto_compile,
+ ACBool),
+ ACBool
+ end,
+
+ BasicHtml =
+ case choose_val(StartOpts#opts.basic_html,
+ SpecStartOpts#opts.basic_html) of
+ undefined ->
+ false;
+ BHBool ->
+ application:set_env(common_test, basic_html,
+ BHBool),
+ BHBool
+ end,
{TS,StartOpts#opts{label = Label,
profile = Profile,
@@ -362,11 +465,15 @@ script_start2(StartOpts = #opts{vts = undefined,
cover = Cover,
logdir = LogDir,
logopts = AllLogOpts,
+ basic_html = BasicHtml,
+ verbosity = AllVerbosity,
config = SpecStartOpts#opts.config,
event_handlers = AllEvHs,
ct_hooks = AllCTHooks,
enable_builtin_hooks =
EnableBuiltinHooks,
+ stylesheet = Stylesheet,
+ auto_compile = AutoCompile,
include = AllInclude,
multiply_timetraps = MultTT,
scale_timetraps = ScaleTT,
@@ -519,6 +626,7 @@ script_start4(#opts{label = Label, profile = Profile,
event_handlers = EvHandlers,
ct_hooks = CTHooks,
logopts = LogOpts,
+ verbosity = Verbosity,
enable_builtin_hooks = EnableBuiltinHooks,
logdir = LogDir, testspecs = Specs}, _Args) ->
%% label - used by ct_logs
@@ -536,7 +644,8 @@ script_start4(#opts{label = Label, profile = Profile,
{ct_hooks, CTHooks},
{enable_builtin_hooks,EnableBuiltinHooks}]) of
ok ->
- ct_util:start(interactive, LogDir),
+ ct_util:start(interactive, LogDir,
+ add_verbosity_defaults(Verbosity)),
ct_util:set_testdata({logopts, LogOpts}),
log_ts_names(Specs),
io:nl(),
@@ -553,7 +662,7 @@ script_start4(#opts{vts = true, cover = Cover}, _) ->
%% Add support later (maybe).
io:format("\nCan't run cover in vts mode.\n\n", [])
end,
- erlang:halt();
+ {error,no_cover_in_vts_mode};
script_start4(#opts{shell = true, cover = Cover}, _) ->
case Cover of
@@ -562,7 +671,8 @@ script_start4(#opts{shell = true, cover = Cover}, _) ->
_ ->
%% Add support later (maybe).
io:format("\nCan't run cover in interactive mode.\n\n", [])
- end;
+ end,
+ {error,no_cover_in_interactive_mode};
script_start4(Opts = #opts{tests = Tests}, Args) ->
do_run(Tests, [], Opts, Args).
@@ -579,6 +689,7 @@ script_usage() ->
"\n\t[-dir TestDir1 TestDir2 .. TestDirN] |"
"\n\t[-suite Suite [-case Case]]"
"\n\t[-logopts LogOpt1 LogOpt2 .. LogOptN]"
+ "\n\t[-verbosity GenVLvl | [CategoryVLvl1 .. CategoryVLvlN]]"
"\n\t[-include InclDir1 InclDir2 .. InclDirN]"
"\n\t[-no_auto_compile]"
"\n\t[-multiply_timetraps N]"
@@ -593,11 +704,12 @@ script_usage() ->
"\n\t[-userconfig CallbackModule ConfigFile1 .. ConfigFileN]"
"\n\t[-decrypt_key Key] | [-decrypt_file KeyFile]"
"\n\t[-logdir LogDir]"
+ "\n\t[-logopts LogOpt1 LogOpt2 .. LogOptN]"
+ "\n\t[-verbosity GenVLvl | [CategoryVLvl1 .. CategoryVLvlN]]"
"\n\t[-silent_connections [ConnType1 ConnType2 .. ConnTypeN]]"
- "\n\t[-stylesheet CSSFile]"
+ "\n\t[-stylesheet CSSFile]"
"\n\t[-cover CoverCfgFile]"
"\n\t[-event_handler EvHandler1 EvHandler2 .. EvHandlerN]"
- "\n\t[-logopts LogOpt1 LogOpt2 .. LogOptN]"
"\n\t[-ct_hooks CTHook1 CTHook2 .. CTHookN]"
"\n\t[-include InclDir1 InclDir2 .. InclDirN]"
"\n\t[-no_auto_compile]"
@@ -613,12 +725,13 @@ script_usage() ->
"\n\t[-config ConfigFile1 ConfigFile2 .. ConfigFileN]"
"\n\t[-decrypt_key Key] | [-decrypt_file KeyFile]"
"\n\t[-logdir LogDir]"
+ "\n\t[-logopts LogOpt1 LogOpt2 .. LogOptN]"
+ "\n\t[-verbosity GenVLvl | [CategoryVLvl1 .. CategoryVLvlN]]"
"\n\t[-allow_user_terms]"
"\n\t[-silent_connections [ConnType1 ConnType2 .. ConnTypeN]]"
"\n\t[-stylesheet CSSFile]"
"\n\t[-cover CoverCfgFile]"
"\n\t[-event_handler EvHandler1 EvHandler2 .. EvHandlerN]"
- "\n\t[-logopts LogOpt1 LogOpt2 .. LogOptN]"
"\n\t[-ct_hooks CTHook1 CTHook2 .. CTHookN]"
"\n\t[-include InclDir1 InclDir2 .. InclDirN]"
"\n\t[-no_auto_compile]"
@@ -702,7 +815,7 @@ run_test(StartOpts) when is_list(StartOpts) ->
Ref = monitor(process, CTPid),
receive
{'DOWN',Ref,process,CTPid,{user_error,Error}} ->
- Error;
+ {error,Error};
{'DOWN',Ref,process,CTPid,Other} ->
Other
end.
@@ -739,8 +852,10 @@ run_test2(StartOpts) ->
(Lbl) when is_atom(Lbl) -> atom_to_list(Lbl)
end, StartOpts),
%% profile
- Profile = get_start_opt(profile, fun(Prof) when is_list(Prof) -> Prof;
- (Prof) when is_atom(Prof) -> atom_to_list(Prof)
+ Profile = get_start_opt(profile, fun(Prof) when is_list(Prof) ->
+ Prof;
+ (Prof) when is_atom(Prof) ->
+ atom_to_list(Prof)
end, StartOpts),
%% logdir
LogDir = get_start_opt(logdir, fun(LD) when is_list(LD) -> LD end,
@@ -748,6 +863,19 @@ run_test2(StartOpts) ->
%% logopts
LogOpts = get_start_opt(logopts, value, [], StartOpts),
+ %% verbosity
+ Verbosity =
+ get_start_opt(verbosity,
+ fun(VLvls) when is_list(VLvls) ->
+ lists:map(fun(VLvl = {_Cat,_Lvl}) ->
+ VLvl;
+ (Lvl) ->
+ {'$unspecified',Lvl}
+ end, VLvls);
+ (VLvl) when is_integer(VLvl) ->
+ [{'$unspecified',VLvl}]
+ end, [], StartOpts),
+
%% config & userconfig
CfgFiles = ct_config:get_config_file_list(StartOpts),
@@ -805,7 +933,7 @@ run_test2(StartOpts) ->
CreatePrivDir = get_start_opt(create_priv_dir, value, StartOpts),
%% auto compile & include files
- Include =
+ {AutoCompile,Include} =
case proplists:get_value(auto_compile, StartOpts) of
undefined ->
application:set_env(common_test, auto_compile, true),
@@ -821,16 +949,16 @@ run_test2(StartOpts) ->
case os:getenv("CT_INCLUDE_PATH") of
false ->
application:set_env(common_test, include, InclDirs),
- InclDirs;
+ {undefined,InclDirs};
CtInclPath ->
InclDirs1 = string:tokens(CtInclPath, [$:,$ ,$,]),
AllInclDirs = InclDirs1++InclDirs,
application:set_env(common_test, include, AllInclDirs),
- AllInclDirs
+ {undefined,AllInclDirs}
end;
ACBool ->
application:set_env(common_test, auto_compile, ACBool),
- []
+ {ACBool,[]}
end,
%% decrypt config file
@@ -844,11 +972,14 @@ run_test2(StartOpts) ->
end,
%% basic html - used by ct_logs
- case proplists:get_value(basic_html, StartOpts) of
- undefined ->
- application:set_env(common_test, basic_html, false);
- BasicHtmlBool ->
- application:set_env(common_test, basic_html, BasicHtmlBool)
+ BasicHtml =
+ case proplists:get_value(basic_html, StartOpts) of
+ undefined ->
+ application:set_env(common_test, basic_html, false),
+ undefined;
+ BasicHtmlBool ->
+ application:set_env(common_test, basic_html, BasicHtmlBool),
+ BasicHtmlBool
end,
%% stepped execution
@@ -856,10 +987,13 @@ run_test2(StartOpts) ->
Opts = #opts{label = Label, profile = Profile,
cover = Cover, step = Step, logdir = LogDir,
- logopts = LogOpts, config = CfgFiles,
+ logopts = LogOpts, basic_html = BasicHtml,
+ config = CfgFiles,
+ verbosity = Verbosity,
event_handlers = EvHandlers,
ct_hooks = CTHooks,
enable_builtin_hooks = EnableBuiltinHooks,
+ auto_compile = AutoCompile,
include = Include,
silent_connections = SilentConns,
stylesheet = Stylesheet,
@@ -894,7 +1028,7 @@ run_spec_file(Relaxed,
log_ts_names(AbsSpecs),
case catch ct_testspec:collect_tests_from_file(AbsSpecs, Relaxed) of
{Error,CTReason} when Error == error ; Error == 'EXIT' ->
- exit(CTReason);
+ exit({error,CTReason});
TS ->
SpecOpts = get_data_for_node(TS, node()),
Label = choose_val(Opts#opts.label,
@@ -905,6 +1039,10 @@ run_spec_file(Relaxed,
SpecOpts#opts.logdir),
AllLogOpts = merge_vals([Opts#opts.logopts,
SpecOpts#opts.logopts]),
+ Stylesheet = choose_val(Opts#opts.stylesheet,
+ SpecOpts#opts.stylesheet),
+ AllVerbosity = merge_keyvals([Opts#opts.verbosity,
+ SpecOpts#opts.verbosity]),
AllConfig = merge_vals([CfgFiles, SpecOpts#opts.config]),
Cover = choose_val(Opts#opts.cover,
SpecOpts#opts.cover),
@@ -918,21 +1056,44 @@ run_spec_file(Relaxed,
SpecOpts#opts.event_handlers]),
AllInclude = merge_vals([Opts#opts.include,
SpecOpts#opts.include]),
-
AllCTHooks = merge_vals([Opts#opts.ct_hooks,
- SpecOpts#opts.ct_hooks]),
+ SpecOpts#opts.ct_hooks]),
EnableBuiltinHooks = choose_val(Opts#opts.enable_builtin_hooks,
SpecOpts#opts.enable_builtin_hooks),
application:set_env(common_test, include, AllInclude),
+ AutoCompile = case choose_val(Opts#opts.auto_compile,
+ SpecOpts#opts.auto_compile) of
+ undefined ->
+ true;
+ ACBool ->
+ application:set_env(common_test, auto_compile,
+ ACBool),
+ ACBool
+ end,
+
+ BasicHtml = case choose_val(Opts#opts.basic_html,
+ SpecOpts#opts.basic_html) of
+ undefined ->
+ false;
+ BHBool ->
+ application:set_env(common_test, basic_html,
+ BHBool),
+ BHBool
+ end,
+
Opts1 = Opts#opts{label = Label,
profile = Profile,
cover = Cover,
logdir = which(logdir, LogDir),
logopts = AllLogOpts,
+ stylesheet = Stylesheet,
+ basic_html = BasicHtml,
+ verbosity = AllVerbosity,
config = AllConfig,
event_handlers = AllEvHs,
+ auto_compile = AutoCompile,
include = AllInclude,
testspecs = AbsSpecs,
multiply_timetraps = MultTT,
@@ -948,20 +1109,20 @@ run_spec_file(Relaxed,
{Run,Skip} = ct_testspec:prepare_tests(TS, node()),
reformat_result(catch do_run(Run, Skip, Opts1, StartOpts));
{error,GCFReason} ->
- exit(GCFReason)
+ exit({error,GCFReason})
end
end.
run_prepared(Run, Skip, Opts = #opts{logdir = LogDir,
- config = CfgFiles },
+ config = CfgFiles},
StartOpts) ->
LogDir1 = which(logdir, LogDir),
case check_and_install_configfiles(CfgFiles, LogDir1, Opts) of
ok ->
reformat_result(catch do_run(Run, Skip, Opts#opts{logdir = LogDir1},
StartOpts));
- {error,Reason} ->
- exit(Reason)
+ {error,_Reason} = Error ->
+ exit(Error)
end.
check_config_file(Callback, File)->
@@ -969,7 +1130,7 @@ check_config_file(Callback, File)->
false ->
case code:load_file(Callback) of
{module,_} -> ok;
- {error,Why} -> exit({cant_load_callback_module,Why})
+ {error,Why} -> exit({error,{cant_load_callback_module,Why}})
end;
_ ->
ok
@@ -980,16 +1141,17 @@ check_config_file(Callback, File)->
{ok,{config,_}}->
File;
{error,{wrong_config,Message}}->
- exit({wrong_config,{Callback,Message}});
+ exit({error,{wrong_config,{Callback,Message}}});
{error,{nofile,File}}->
- exit({no_such_file,?abs(File)})
+ exit({error,{no_such_file,?abs(File)}})
end.
run_dir(Opts = #opts{logdir = LogDir,
config = CfgFiles,
event_handlers = EvHandlers,
ct_hooks = CTHook,
- enable_builtin_hooks = EnableBuiltinHooks }, StartOpts) ->
+ enable_builtin_hooks = EnableBuiltinHooks},
+ StartOpts) ->
LogDir1 = which(logdir, LogDir),
Opts1 = Opts#opts{logdir = LogDir1},
AbsCfgFiles =
@@ -1002,7 +1164,8 @@ run_dir(Opts = #opts{logdir = LogDir,
{module,Callback}->
ok;
{error,_}->
- exit({no_such_module,Callback})
+ exit({error,{no_such_module,
+ Callback}})
end
end,
{Callback,
@@ -1015,7 +1178,7 @@ run_dir(Opts = #opts{logdir = LogDir,
{ct_hooks, CTHook},
{enable_builtin_hooks,EnableBuiltinHooks}], LogDir1) of
ok -> ok;
- {error,IReason} -> exit(IReason)
+ {error,_IReason} = IError -> exit(IError)
end,
case {proplists:get_value(dir, StartOpts),
proplists:get_value(suite, StartOpts),
@@ -1057,7 +1220,7 @@ run_dir(Opts = #opts{logdir = LogDir,
[], Opts1, StartOpts));
{undefined,[Hd,_|_],_GsAndCs} when not is_integer(Hd) ->
- exit(multiple_suites_and_cases);
+ exit({error,multiple_suites_and_cases});
{undefined,Suite=[Hd|Tl],GsAndCs} when is_integer(Hd) ;
(is_list(Hd) and (Tl == [])) ;
@@ -1067,10 +1230,10 @@ run_dir(Opts = #opts{logdir = LogDir,
[], Opts1, StartOpts));
{[Hd,_|_],_Suites,[]} when is_list(Hd) ; not is_integer(Hd) ->
- exit(multiple_dirs_and_suites);
+ exit({error,multiple_dirs_and_suites});
{undefined,undefined,GsAndCs} when GsAndCs /= [] ->
- exit(incorrect_start_options);
+ exit({error,incorrect_start_options});
{Dir,Suite,GsAndCs} when is_integer(hd(Dir)) ;
(is_atom(Dir) and (Dir /= undefined)) ;
@@ -1079,7 +1242,7 @@ run_dir(Opts = #opts{logdir = LogDir,
Dir1 = if is_atom(Dir) -> atom_to_list(Dir);
true -> Dir end,
if Suite == undefined ->
- exit(incorrect_start_options);
+ exit({error,incorrect_start_options});
is_integer(hd(Suite)) ;
(is_atom(Suite) and (Suite /= undefined)) ;
@@ -1098,7 +1261,7 @@ run_dir(Opts = #opts{logdir = LogDir,
is_list(Suite) -> % multiple suites
case [suite_to_test(Dir1, S) || S <- Suite] of
[_,_|_] when GsAndCs /= [] ->
- exit(multiple_suites_and_cases);
+ exit({error,multiple_suites_and_cases});
[{Dir2,Mod}] when GsAndCs /= [] ->
reformat_result(catch do_run(tests(Dir2, Mod, GsAndCs),
[], Opts1, StartOpts));
@@ -1109,10 +1272,10 @@ run_dir(Opts = #opts{logdir = LogDir,
end;
{undefined,undefined,[]} ->
- exit(no_test_specified);
+ exit({error,no_test_specified});
{Dir,Suite,GsAndCs} ->
- exit({incorrect_start_options,{Dir,Suite,GsAndCs}})
+ exit({error,{incorrect_start_options,{Dir,Suite,GsAndCs}}})
end.
%%%-----------------------------------------------------------------
@@ -1157,7 +1320,7 @@ run_testspec2(File) when is_list(File), is_integer(hd(File)) ->
run_testspec2(TestSpec) ->
case catch ct_testspec:collect_tests_from_list(TestSpec, false) of
{E,CTReason} when E == error ; E == 'EXIT' ->
- exit(CTReason);
+ exit({error,CTReason});
TS ->
Opts = get_data_for_node(TS, node()),
@@ -1179,8 +1342,8 @@ run_testspec2(TestSpec) ->
include = AllInclude},
{Run,Skip} = ct_testspec:prepare_tests(TS, node()),
reformat_result(catch do_run(Run, Skip, Opts1, []));
- {error,GCFReason} ->
- exit(GCFReason)
+ {error,_GCFReason} = GCFError ->
+ exit(GCFError)
end
end.
@@ -1188,12 +1351,16 @@ get_data_for_node(#testspec{label = Labels,
profile = Profiles,
logdir = LogDirs,
logopts = LogOptsList,
+ basic_html = BHs,
+ stylesheet = SSs,
+ verbosity = VLvls,
cover = CoverFs,
config = Cfgs,
userconfig = UsrCfgs,
event_handler = EvHs,
ct_hooks = CTHooks,
enable_builtin_hooks = EnableBuiltinHooks,
+ auto_compile = ACs,
include = Incl,
multiply_timetraps = MTs,
scale_timetraps = STs,
@@ -1208,6 +1375,12 @@ get_data_for_node(#testspec{label = Labels,
undefined -> [];
LOs -> LOs
end,
+ BasicHtml = proplists:get_value(Node, BHs),
+ Stylesheet = proplists:get_value(Node, SSs),
+ Verbosity = case proplists:get_value(Node, VLvls) of
+ undefined -> [];
+ Lvls -> Lvls
+ end,
Cover = proplists:get_value(Node, CoverFs),
MT = proplists:get_value(Node, MTs),
ST = proplists:get_value(Node, STs),
@@ -1216,16 +1389,21 @@ get_data_for_node(#testspec{label = Labels,
[CBF || {N,CBF} <- UsrCfgs, N==Node],
EvHandlers = [{H,A} || {N,H,A} <- EvHs, N==Node],
FiltCTHooks = [Hook || {N,Hook} <- CTHooks, N==Node],
+ AutoCompile = proplists:get_value(Node, ACs),
Include = [I || {N,I} <- Incl, N==Node],
#opts{label = Label,
profile = Profile,
logdir = LogDir,
logopts = LogOpts,
+ basic_html = BasicHtml,
+ stylesheet = Stylesheet,
+ verbosity = Verbosity,
cover = Cover,
config = ConfigFiles,
event_handlers = EvHandlers,
ct_hooks = FiltCTHooks,
enable_builtin_hooks = EnableBuiltinHooks,
+ auto_compile = AutoCompile,
include = Include,
multiply_timetraps = MT,
scale_timetraps = ST,
@@ -1267,6 +1445,14 @@ choose_val(V0, _V1) ->
merge_vals(Vs) ->
lists:append(Vs).
+merge_keyvals(Vs) ->
+ make_unique(lists:append(Vs)).
+
+make_unique([Elem={Key,_} | Elems]) ->
+ [Elem | make_unique(proplists:delete(Key, Elems))];
+make_unique([]) ->
+ [].
+
listify([C|_]=Str) when is_integer(C) -> [Str];
listify(L) when is_list(L) -> L;
listify(E) -> [E].
@@ -1376,7 +1562,8 @@ do_run(Tests, Misc, LogDir, LogOpts) when is_list(Misc),
do_run(Tests, [], Opts1#opts{logdir = LogDir}, []);
do_run(Tests, Skip, Opts, Args) when is_record(Opts, opts) ->
- #opts{label = Label, profile = Profile, cover = Cover} = Opts,
+ #opts{label = Label, profile = Profile, cover = Cover,
+ verbosity = VLvls} = Opts,
%% label - used by ct_logs
TestLabel =
if Label == undefined -> undefined;
@@ -1397,7 +1584,7 @@ do_run(Tests, Skip, Opts, Args) when is_record(Opts, opts) ->
case code:which(test_server) of
non_existing ->
- exit({error,no_path_to_test_server});
+ {error,no_path_to_test_server};
_ ->
Opts1 = if Cover == undefined ->
Opts;
@@ -1418,77 +1605,126 @@ do_run(Tests, Skip, Opts, Args) when is_record(Opts, opts) ->
"ct_framework" ->
ok;
Other ->
- erlang:display(list_to_atom("Note: TEST_SERVER_FRAMEWORK = " ++ Other))
+ erlang:display(
+ list_to_atom(
+ "Note: TEST_SERVER_FRAMEWORK = " ++ Other))
end,
- case ct_util:start(Opts#opts.logdir) of
+ Verbosity = add_verbosity_defaults(VLvls),
+ case ct_util:start(Opts#opts.logdir, Verbosity) of
{error,interactive_mode} ->
io:format("CT is started in interactive mode. "
- "To exit this mode, run ct:stop_interactive().\n"
+ "To exit this mode, "
+ "run ct:stop_interactive().\n"
"To enter the interactive mode again, "
"run ct:start_interactive()\n\n",[]),
{error,interactive_mode};
_Pid ->
- %% save stylesheet info
- ct_util:set_testdata({stylesheet,Opts#opts.stylesheet}),
- %% save logopts
- ct_util:set_testdata({logopts,Opts#opts.logopts}),
- %% enable silent connections
- case Opts#opts.silent_connections of
- [] ->
- Conns = ct_util:override_silence_all_connections(),
- ct_logs:log("Silent connections", "~p", [Conns]);
- Conns when is_list(Conns) ->
- ct_util:override_silence_connections(Conns),
- ct_logs:log("Silent connections", "~p", [Conns]);
- _ ->
- ok
- end,
- log_ts_names(Opts1#opts.testspecs),
- TestSuites = suite_tuples(Tests),
-
- {_TestSuites1,SuiteMakeErrors,AllMakeErrors} =
- case application:get_env(common_test, auto_compile) of
- {ok,false} ->
- {TestSuites1,SuitesNotFound} =
- verify_suites(TestSuites),
- {TestSuites1,SuitesNotFound,SuitesNotFound};
- _ ->
- {SuiteErrs,HelpErrs} = auto_compile(TestSuites),
- {TestSuites,SuiteErrs,SuiteErrs++HelpErrs}
- end,
+ compile_and_run(Tests, Skip,
+ Opts1#opts{verbosity=Verbosity}, Args)
+ end
+ end.
- case continue(AllMakeErrors) of
- true ->
- SavedErrors = save_make_errors(SuiteMakeErrors),
- ct_repeat:log_loop_info(Args),
+compile_and_run(Tests, Skip, Opts, Args) ->
+ %% save stylesheet info
+ ct_util:set_testdata({stylesheet,Opts#opts.stylesheet}),
+ %% save logopts
+ ct_util:set_testdata({logopts,Opts#opts.logopts}),
+ %% enable silent connections
+ case Opts#opts.silent_connections of
+ [] ->
+ Conns = ct_util:override_silence_all_connections(),
+ ct_logs:log("Silent connections", "~p", [Conns]);
+ Conns when is_list(Conns) ->
+ ct_util:override_silence_connections(Conns),
+ ct_logs:log("Silent connections", "~p", [Conns]);
+ _ ->
+ ok
+ end,
+ log_ts_names(Opts#opts.testspecs),
+ TestSuites = suite_tuples(Tests),
+
+ {_TestSuites1,SuiteMakeErrors,AllMakeErrors} =
+ case application:get_env(common_test, auto_compile) of
+ {ok,false} ->
+ {TestSuites1,SuitesNotFound} =
+ verify_suites(TestSuites),
+ {TestSuites1,SuitesNotFound,SuitesNotFound};
+ _ ->
+ {SuiteErrs,HelpErrs} = auto_compile(TestSuites),
+ {TestSuites,SuiteErrs,SuiteErrs++HelpErrs}
+ end,
+
+ case continue(AllMakeErrors) of
+ true ->
+ SavedErrors = save_make_errors(SuiteMakeErrors),
+ ct_repeat:log_loop_info(Args),
+
+ {Tests1,Skip1} = final_tests(Tests,Skip,SavedErrors),
+
+ possibly_spawn(true == proplists:get_value(noinput, Args),
+ Tests1, Skip1, Opts);
+ false ->
+ io:nl(),
+ ct_util:stop(clean),
+ BadMods =
+ lists:foldr(
+ fun({{_,_},Ms}, Acc) ->
+ Ms ++ lists:foldl(
+ fun(M, Acc1) ->
+ lists:delete(M, Acc1)
+ end, Acc, Ms)
+ end, [], AllMakeErrors),
+ {error,{make_failed,BadMods}}
+ end.
- {Tests1,Skip1} = final_tests(Tests,Skip,SavedErrors),
+%% keep the shell as the top controlling process
+possibly_spawn(false, Tests, Skip, Opts) ->
+ TestResult = (catch do_run_test(Tests, Skip, Opts)),
+ case TestResult of
+ {EType,_} = Error when EType == user_error;
+ EType == error ->
+ ct_util:stop(clean),
+ exit(Error);
+ _ ->
+ ct_util:stop(normal),
+ TestResult
+ end;
- R = (catch do_run_test(Tests1, Skip1, Opts1)),
- case R of
- {EType,_} = Error when EType == user_error ;
+%% we must return control to the shell now, so we spawn
+%% a test supervisor process to keep an eye on the test run
+possibly_spawn(true, Tests, Skip, Opts) ->
+ CTUtilSrv = whereis(ct_util_server),
+ Supervisor =
+ fun() ->
+ process_flag(trap_exit, true),
+ link(CTUtilSrv),
+ TestRun =
+ fun() ->
+ TestResult = (catch do_run_test(Tests, Skip, Opts)),
+ case TestResult of
+ {EType,_} = Error when EType == user_error;
EType == error ->
ct_util:stop(clean),
exit(Error);
_ ->
ct_util:stop(normal),
- R
- end;
- false ->
- io:nl(),
- ct_util:stop(clean),
- BadMods =
- lists:foldr(
- fun({{_,_},Ms}, Acc) ->
- Ms ++ lists:foldl(
- fun(M, Acc1) ->
- lists:delete(M, Acc1)
- end, Acc, Ms)
- end, [], AllMakeErrors),
- {error,{make_failed,BadMods}}
- end
- end
- end.
+ exit({ok,TestResult})
+ end
+ end,
+ TestRunPid = spawn_link(TestRun),
+ receive
+ {'EXIT',TestRunPid,{ok,TestResult}} ->
+ io:format(user, "~nCommon Test returned ~p~n~n",
+ [TestResult]);
+ {'EXIT',TestRunPid,Error} ->
+ exit(Error)
+ end
+ end,
+ unlink(CTUtilSrv),
+ SupPid = spawn(Supervisor),
+ io:format(user, "~nTest control handed over to process ~p~n~n",
+ [SupPid]),
+ SupPid.
%% attempt to compile the modules specified in TestSuites
auto_compile(TestSuites) ->
@@ -1504,11 +1740,13 @@ auto_compile(TestSuites) ->
end,
SuiteMakeErrors =
lists:flatmap(fun({TestDir,Suite} = TS) ->
- case run_make(suites, TestDir, Suite, UserInclude) of
+ case run_make(suites, TestDir,
+ Suite, UserInclude) of
{error,{make_failed,Bad}} ->
[{TS,Bad}];
{error,_} ->
- [{TS,[filename:join(TestDir,"*_SUITE")]}];
+ [{TS,[filename:join(TestDir,
+ "*_SUITE")]}];
_ ->
[]
end
@@ -1547,23 +1785,29 @@ verify_suites(TestSuites) ->
{[DS|Found],NotFound};
true ->
Beam = filename:join(TestDir,
- atom_to_list(Suite)++".beam"),
+ atom_to_list(Suite)++
+ ".beam"),
case filelib:is_regular(Beam) of
true ->
{[DS|Found],NotFound};
false ->
case code:is_loaded(Suite) of
{file,SuiteFile} ->
- %% test suite is already loaded and
- %% since auto_compile == false,
+ %% test suite is already
+ %% loaded and since
+ %% auto_compile == false,
%% let's assume the user has
- %% loaded the beam file explicitly
- ActualDir = filename:dirname(SuiteFile),
- {[{ActualDir,Suite}|Found],NotFound};
+ %% loaded the beam file
+ %% explicitly
+ ActualDir =
+ filename:dirname(SuiteFile),
+ {[{ActualDir,Suite}|Found],
+ NotFound};
false ->
Name =
filename:join(TestDir,
- atom_to_list(Suite)),
+ atom_to_list(
+ Suite)),
io:format(user,
"Suite ~w not found"
"in directory ~s~n",
@@ -1581,7 +1825,8 @@ verify_suites(TestSuites) ->
ActualDir = filename:dirname(SuiteFile),
{[{ActualDir,Suite}|Found],NotFound};
false ->
- io:format(user, "Directory ~s is invalid~n", [Dir]),
+ io:format(user, "Directory ~s is "
+ "invalid~n", [Dir]),
Name = filename:join(Dir, atom_to_list(Suite)),
{Found,[{DS,[Name]}|NotFound]}
end
@@ -1595,7 +1840,8 @@ save_make_errors([]) ->
save_make_errors(Errors) ->
Suites = get_bad_suites(Errors,[]),
ct_logs:log("MAKE RESULTS",
- "Error compiling or locating the following suites: ~n~p",[Suites]),
+ "Error compiling or locating the "
+ "following suites: ~n~p",[Suites]),
%% save the info for logger
file:write_file(?missing_suites_info,term_to_binary(Errors)),
Errors.
@@ -1616,8 +1862,9 @@ step(TestDir, Suite, Case) ->
%%%-----------------------------------------------------------------
%%% @hidden
%%% @equiv ct:step/4
-step(TestDir, Suite, Case, Opts) when is_list(TestDir), is_atom(Suite), is_atom(Case),
- Suite =/= all, Case =/= all ->
+step(TestDir, Suite, Case, Opts) when is_list(TestDir),
+ is_atom(Suite), is_atom(Case),
+ Suite =/= all, Case =/= all ->
do_run([{TestDir,Suite,Case}], [{step,Opts}]).
@@ -1735,9 +1982,11 @@ continue(_MakeErrors) ->
case set_group_leader_same_as_shell() of
true ->
S = self(),
- io:format("Failed to compile or locate one or more test suites\n"
+ io:format("Failed to compile or locate one "
+ "or more test suites\n"
"Press \'c\' to continue or \'a\' to abort.\n"
- "Will continue in 15 seconds if no answer is given!\n"),
+ "Will continue in 15 seconds if no "
+ "answer is given!\n"),
Pid = spawn(fun() ->
case io:get_line('(c/a) ') of
"c\n" ->
@@ -1769,7 +2018,8 @@ set_group_leader_same_as_shell() ->
end
end,
case [P || P <- processes(), GS2or3(P),
- true == lists:keymember(shell,1,element(2,process_info(P,dictionary)))] of
+ true == lists:keymember(shell,1,
+ element(2,process_info(P,dictionary)))] of
[GL|_] ->
group_leader(GL, self());
[] ->
@@ -1815,12 +2065,14 @@ do_run_test(Tests, Skip, Opts) ->
incl_mods = CovIncl,
cross = CovCross,
src = _CovSrc}} ->
- ct_logs:log("COVER INFO","Using cover specification file: ~s~n"
+ ct_logs:log("COVER INFO",
+ "Using cover specification file: ~s~n"
"App: ~w~n"
"Cross cover: ~w~n"
"Including ~w modules~n"
"Excluding ~w modules",
- [CovFile,CovApp,CovCross,length(CovIncl),length(CovExcl)]),
+ [CovFile,CovApp,CovCross,
+ length(CovIncl),length(CovExcl)]),
%% cover export file will be used for export and import
%% between tests so make sure it doesn't exist initially
@@ -1828,7 +2080,8 @@ do_run_test(Tests, Skip, Opts) ->
true ->
DelResult = file:delete(CovExport),
ct_logs:log("COVER INFO",
- "Warning! Export file ~s already exists. "
+ "Warning! "
+ "Export file ~s already exists. "
"Deleting with result: ~p",
[CovExport,DelResult]);
false ->
@@ -1844,7 +2097,8 @@ do_run_test(Tests, Skip, Opts) ->
%% start cover on specified nodes
if (CovNodes /= []) and (CovNodes /= undefined) ->
ct_logs:log("COVER INFO",
- "Nodes included in cover session: ~w",
+ "Nodes included in cover "
+ "session: ~w",
[CovNodes]),
cover:start(CovNodes);
true ->
@@ -1869,17 +2123,27 @@ do_run_test(Tests, Skip, Opts) ->
ct_logs:log("TEST INFO","~w test(s), ~w suite(s)",
[NoOfTests,NoOfSuites]);
true ->
- io:format("~nTEST INFO: ~w test(s), ~w case(s) in ~w suite(s)~n~n",
+ io:format("~nTEST INFO: ~w test(s), ~w case(s) "
+ "in ~w suite(s)~n~n",
[NoOfTests,NoOfCases,NoOfSuites]),
- ct_logs:log("TEST INFO","~w test(s), ~w case(s) in ~w suite(s)",
+ ct_logs:log("TEST INFO","~w test(s), ~w case(s) "
+ "in ~w suite(s)",
[NoOfTests,NoOfCases,NoOfSuites])
end,
-
+ %% if the verbosity level is set lower than ?STD_IMPORTANCE, tell
+ %% test_server to ignore stdout printouts to the test case log file
+ case proplists:get_value(default, Opts#opts.verbosity) of
+ VLvl when is_integer(VLvl), (?STD_IMPORTANCE < (100-VLvl)) ->
+ test_server_ctrl:reject_io_reqs(true);
+ _Lower ->
+ ok
+ end,
test_server_ctrl:multiply_timetraps(Opts#opts.multiply_timetraps),
test_server_ctrl:scale_timetraps(Opts#opts.scale_timetraps),
- test_server_ctrl:create_priv_dir(choose_val(Opts#opts.create_priv_dir,
- auto_per_run)),
+ test_server_ctrl:create_priv_dir(choose_val(
+ Opts#opts.create_priv_dir,
+ auto_per_run)),
ct_event:notify(#event{name=start_info,
node=node(),
data={NoOfTests,NoOfSuites,NoOfCases}}),
@@ -1898,9 +2162,15 @@ do_run_test(Tests, Skip, Opts) ->
maybe_cleanup_interpret(Suite, Opts#opts.step)
end, CleanUp),
[code:del_path(Dir) || Dir <- AddedToPath],
- ok;
+
+ case ct_util:get_testdata(stats) of
+ Stats = {_Ok,_Failed,{_UserSkipped,_AutoSkipped}} ->
+ Stats;
+ _ ->
+ {error,test_result_unknown}
+ end;
Error ->
- Error
+ exit(Error)
end.
delete_dups([S | Suites]) ->
@@ -2357,7 +2627,6 @@ parse_cth_args(String) ->
String
end.
-
event_handler_args2opts(Args) ->
case proplists:get_value(event_handler, Args) of
undefined ->
@@ -2380,6 +2649,42 @@ event_handler_init_args2opts([EH, Arg]) ->
event_handler_init_args2opts([]) ->
[].
+verbosity_args2opts(Args) ->
+ case proplists:get_value(verbosity, Args) of
+ undefined ->
+ [];
+ VArgs ->
+ GetVLvls =
+ fun("and", {new,SoFar}) when is_list(SoFar) ->
+ {new,SoFar};
+ ("and", {Lvl,SoFar}) when is_list(SoFar) ->
+ {new,[{'$unspecified',list_to_integer(Lvl)} | SoFar]};
+ (CatOrLvl, {new,SoFar}) when is_list(SoFar) ->
+ {CatOrLvl,SoFar};
+ (Lvl, {Cat,SoFar}) ->
+ {new,[{list_to_atom(Cat),list_to_integer(Lvl)} | SoFar]}
+ end,
+ case lists:foldl(GetVLvls, {new,[]}, VArgs) of
+ {new,Parsed} ->
+ Parsed;
+ {Lvl,Parsed} ->
+ [{'$unspecified',list_to_integer(Lvl)} | Parsed]
+ end
+ end.
+
+add_verbosity_defaults(VLvls) ->
+ case {proplists:get_value('$unspecified', VLvls),
+ proplists:get_value(default, VLvls)} of
+ {undefined,undefined} ->
+ ?default_verbosity ++ VLvls;
+ {Lvl,undefined} ->
+ [{default,Lvl} | VLvls];
+ {undefined,_Lvl} ->
+ [{'$unspecified',?MAX_VERBOSITY} | VLvls];
+ _ ->
+ VLvls
+ end.
+
%% This function reads pa and pz arguments, converts dirs from relative
%% to absolute, and re-inserts them in the code path. The order of the
%% dirs in the code path remain the same. Note however that since this
@@ -2446,7 +2751,11 @@ make_abs1([], Path) ->
%% to ct_run start arguments (on the init arguments format) -
%% this is useful mainly for testing the ct_run start functions.
opts2args(EnvStartOpts) ->
- lists:flatmap(fun({config,CfgFiles}) ->
+ lists:flatmap(fun({exit_status,ExitStatusOpt}) when is_atom(ExitStatusOpt) ->
+ [{exit_status,[atom_to_list(ExitStatusOpt)]}];
+ ({halt_with,{HaltM,HaltF}}) ->
+ [{halt_with,[atom_to_list(HaltM),atom_to_list(HaltF)]}];
+ ({config,CfgFiles}) ->
[{ct_config,[CfgFiles]}];
({userconfig,{CBM,CfgStr=[X|_]}}) when is_integer(X) ->
[{userconfig,[atom_to_list(CBM),CfgStr]}];
@@ -2454,10 +2763,14 @@ opts2args(EnvStartOpts) ->
[{userconfig,[atom_to_list(CBM) | CfgStrs]}];
({userconfig,UserCfg}) when is_list(UserCfg) ->
Strs =
- lists:map(fun({CBM,CfgStr=[X|_]}) when is_integer(X) ->
- [atom_to_list(CBM),CfgStr,"and"];
- ({CBM,CfgStrs}) when is_list(CfgStrs) ->
- [atom_to_list(CBM) | CfgStrs] ++ ["and"]
+ lists:map(fun({CBM,CfgStr=[X|_]})
+ when is_integer(X) ->
+ [atom_to_list(CBM),
+ CfgStr,"and"];
+ ({CBM,CfgStrs})
+ when is_list(CfgStrs) ->
+ [atom_to_list(CBM) | CfgStrs] ++
+ ["and"]
end, UserCfg),
[_LastAnd|StrsR] = lists:reverse(lists:flatten(Strs)),
[{userconfig,lists:reverse(StrsR)}];
@@ -2492,7 +2805,7 @@ opts2args(EnvStartOpts) ->
({decrypt,{file,File}}) ->
[{ct_decrypt_file,[File]}];
({basic_html,true}) ->
- ({basic_html,[]});
+ [{basic_html,[]}];
({basic_html,false}) ->
[];
({event_handler,EH}) when is_atom(EH) ->
@@ -2505,12 +2818,32 @@ opts2args(EnvStartOpts) ->
({event_handler,{EHs,Arg}}) when is_list(EHs) ->
ArgStr = lists:flatten(io_lib:format("~p", [Arg])),
Strs = lists:map(fun(EH) ->
- [atom_to_list(EH),ArgStr,"and"]
+ [atom_to_list(EH),
+ ArgStr,"and"]
end, EHs),
[_LastAnd|StrsR] = lists:reverse(lists:flatten(Strs)),
[{event_handler_init,lists:reverse(StrsR)}];
({logopts,LOs}) when is_list(LOs) ->
[{logopts,[atom_to_list(LO) || LO <- LOs]}];
+ ({verbosity,?default_verbosity}) ->
+ [];
+ ({verbosity,VLvl}) when is_integer(VLvl) ->
+ [{verbosity,[integer_to_list(VLvl)]}];
+ ({verbosity,VLvls}) when is_list(VLvls) ->
+ VLvlArgs =
+ lists:flatmap(fun({'$unspecified',Lvl}) ->
+ [integer_to_list(Lvl),
+ "and"];
+ ({Cat,Lvl}) ->
+ [atom_to_list(Cat),
+ integer_to_list(Lvl),
+ "and"];
+ (Lvl) ->
+ [integer_to_list(Lvl),
+ "and"]
+ end, VLvls),
+ [_LastAnd|VLvlArgsR] = lists:reverse(VLvlArgs),
+ [{verbosity,lists:reverse(VLvlArgsR)}];
({ct_hooks,[]}) ->
[];
({ct_hooks,CTHs}) when is_list(CTHs) ->
diff --git a/lib/common_test/src/ct_ssh.erl b/lib/common_test/src/ct_ssh.erl
index aebb28bc42..09cd4ef02a 100644
--- a/lib/common_test/src/ct_ssh.erl
+++ b/lib/common_test/src/ct_ssh.erl
@@ -182,19 +182,22 @@ connect(KeyOrName, ConnType, ExtraOpts) ->
undefined ->
{ssh,undefined,AllOpts};
SFTPAddr ->
- log(heading(connect,KeyOrName),
- "Note: Opening ssh connection to sftp host.\n",
+ try_log(heading(connect,KeyOrName),
+ "Note: Opening ssh connection "
+ "to sftp host.\n",
[]),
{ssh,SFTPAddr,
- [{ssh,SFTPAddr}|proplists:delete(sftp, AllOpts)]}
+ [{ssh,SFTPAddr} |
+ proplists:delete(sftp, AllOpts)]}
end;
undefined when ConnType == sftp ->
case proplists:get_value(ssh, AllOpts) of
undefined ->
{sftp,undefined,AllOpts};
SSHAddr ->
- log(heading(connect,KeyOrName),
- "Note: Opening sftp connection to ssh host.\n",
+ try_log(heading(connect,KeyOrName),
+ "Note: Opening sftp connection "
+ "to ssh host.\n",
[]),
{sftp,SSHAddr,
[{sftp,SSHAddr}|proplists:delete(ssh, AllOpts)]}
@@ -209,15 +212,15 @@ connect(KeyOrName, ConnType, ExtraOpts) ->
[{not_available,{KeyOrName,ConnType1}}]),
{error,{not_available,{KeyOrName,ConnType1}}};
{_,undefined} ->
- log(heading(connect,KeyOrName),
- "Opening ~w connection to ~p:22\n",
- [ConnType1,Addr]),
+ try_log(heading(connect,KeyOrName),
+ "Opening ~w connection to ~p:22\n",
+ [ConnType1,Addr]),
ct_gen_conn:start(KeyOrName, {ConnType1,Addr,22},
AllOpts1, ?MODULE);
{_,Port} ->
- log(heading(connect,KeyOrName),
- "Opening ~w connection to ~p:~w\n",
- [ConnType1,Addr,Port]),
+ try_log(heading(connect,KeyOrName),
+ "Opening ~w connection to ~p:~w\n",
+ [ConnType1,Addr,Port]),
ct_gen_conn:start(KeyOrName, {ConnType1,Addr,Port},
AllOpts1, ?MODULE)
end
@@ -232,7 +235,7 @@ connect(KeyOrName, ConnType, ExtraOpts) ->
disconnect(SSH) ->
case get_handle(SSH) of
{ok,Pid} ->
- log(heading(disconnect,SSH), "Handle: ~p", [Pid]),
+ try_log(heading(disconnect,SSH), "Handle: ~p", [Pid], 5000),
case ct_gen_conn:stop(Pid) of
{error,{process_down,Pid,noproc}} ->
{error,already_closed};
@@ -968,8 +971,9 @@ init(KeyOrName, {ConnType,Addr,Port}, AllOpts) ->
Error;
Ok ->
SSHRef = element(2, Ok),
- log(heading(init,KeyOrName),
- "Opened ~w connection:\nHost: ~p (~p)\nUser: ~p\nPassword: ~p\n",
+ try_log(heading(init,KeyOrName),
+ "Opened ~w connection:\n"
+ "Host: ~p (~p)\nUser: ~p\nPassword: ~p\n",
[ConnType,Addr,Port,User,lists:duplicate(length(Password),$*)]),
{ok,SSHRef,#state{ssh_ref=SSHRef, conn_type=ConnType,
target=KeyOrName}}
@@ -978,25 +982,26 @@ init(KeyOrName, {ConnType,Addr,Port}, AllOpts) ->
%% @hidden
handle_msg(sftp_connect, State) ->
#state{ssh_ref=SSHRef, target=Target} = State,
- log(heading(sftp_connect,Target), "SSH Ref: ~p", [SSHRef]),
+ try_log(heading(sftp_connect,Target), "SSH Ref: ~p", [SSHRef]),
{ssh_sftp:start_channel(SSHRef),State};
handle_msg({session_open,TO}, State) ->
#state{ssh_ref=SSHRef, target=Target} = State,
- log(heading(session_open,Target), "SSH Ref: ~p, Timeout: ~p", [SSHRef,TO]),
+ try_log(heading(session_open,Target), "SSH Ref: ~p, Timeout: ~p",
+ [SSHRef,TO]),
{ssh_connection:session_channel(SSHRef, TO),State};
handle_msg({session_close,Chn}, State) ->
#state{ssh_ref=SSHRef, target=Target} = State,
- log(heading(session_close,Target), "SSH Ref: ~p, Chn: ~p", [SSHRef,Chn]),
+ try_log(heading(session_close,Target), "SSH Ref: ~p, Chn: ~p", [SSHRef,Chn]),
{ssh_connection:close(SSHRef, Chn),State};
handle_msg({exec,Chn,Command,TO}, State) ->
#state{ssh_ref=SSHRef, target=Target} = State,
Chn1 =
if Chn == undefined ->
- log(heading(exec,Target),
- "Opening channel for exec, SSH Ref: ~p", [SSHRef]),
+ try_log(heading(exec,Target),
+ "Opening channel for exec, SSH Ref: ~p", [SSHRef]),
case ssh_connection:session_channel(SSHRef, TO) of
{ok,C} -> C;
CErr -> CErr
@@ -1009,9 +1014,9 @@ handle_msg({exec,Chn,Command,TO}, State) ->
log(heading(exec,Target), "Opening channel failed: ~p", [ChnError]),
{ChnError,State};
_ ->
- log(heading(exec,Target),
- "SSH Ref: ~p, Chn: ~p, Command: ~p, Timeout: ~p",
- [SSHRef,Chn1,Command,TO]),
+ try_log(heading(exec,Target),
+ "SSH Ref: ~p, Chn: ~p, Command: ~p, Timeout: ~p",
+ [SSHRef,Chn1,Command,TO]),
case ssh_connection:exec(SSHRef, Chn1, Command, TO) of
success ->
Result = do_recv_response(SSHRef, Chn1, [], close, TO),
@@ -1024,24 +1029,24 @@ handle_msg({exec,Chn,Command,TO}, State) ->
handle_msg({receive_response,Chn,End,TO}, State) ->
#state{ssh_ref=SSHRef, target=Target} = State,
- log(heading(receive_response,Target),
- "SSH Ref: ~p, Chn: ~p, Timeout: ~p", [SSHRef,Chn,TO]),
+ try_log(heading(receive_response,Target),
+ "SSH Ref: ~p, Chn: ~p, Timeout: ~p", [SSHRef,Chn,TO]),
Result = do_recv_response(SSHRef, Chn, [], End, TO),
{Result,State};
handle_msg({send,Chn,Type,Data,TO}, State) ->
#state{ssh_ref=SSHRef, target=Target} = State,
- log(heading(send,Target),
- "SSH Ref: ~p, Chn: ~p, Type: ~p, Timeout: ~p~n"
- "Data: ~p", [SSHRef,Chn,Type,TO,Data]),
+ try_log(heading(send,Target),
+ "SSH Ref: ~p, Chn: ~p, Type: ~p, Timeout: ~p~n"
+ "Data: ~p", [SSHRef,Chn,Type,TO,Data]),
Result = ssh_connection:send(SSHRef, Chn, Type, Data, TO),
{Result,State};
handle_msg({send_and_receive,Chn,Type,Data,End,TO}, State) ->
#state{ssh_ref=SSHRef, target=Target} = State,
- log(heading(send_and_receive,Target),
- "SSH Ref: ~p, Chn: ~p, Type: ~p, Timeout: ~p~n"
- "Data: ~p", [SSHRef,Chn,Type,TO,Data]),
+ try_log(heading(send_and_receive,Target),
+ "SSH Ref: ~p, Chn: ~p, Type: ~p, Timeout: ~p~n"
+ "Data: ~p", [SSHRef,Chn,Type,TO,Data]),
case ssh_connection:send(SSHRef, Chn, Type, Data, TO) of
ok ->
Result = do_recv_response(SSHRef, Chn, [], End, TO),
@@ -1052,137 +1057,162 @@ handle_msg({send_and_receive,Chn,Type,Data,End,TO}, State) ->
handle_msg({subsystem,Chn,Subsystem,TO}, State) ->
#state{ssh_ref=SSHRef, target=Target} = State,
- log(heading(subsystem,Target),
- "SSH Ref: ~p, Chn: ~p, Subsys: ~p, Timeout: ~p",
- [SSHRef,Chn,Subsystem,TO]),
+ try_log(heading(subsystem,Target),
+ "SSH Ref: ~p, Chn: ~p, Subsys: ~p, Timeout: ~p",
+ [SSHRef,Chn,Subsystem,TO]),
Result = ssh_connection:subsystem(SSHRef, Chn, Subsystem, TO),
{Result,State};
%% --- SFTP Commands ---
handle_msg({read_file,Srv,File}=Cmd, S=#state{ssh_ref=SSHRef}) ->
- log(heading(sftp,S#state.target),
- "SSH Ref: ~p, Server: ~p~nCmd: ~p", [SSHRef,ref(Srv,SSHRef),mod(Cmd)]),
+ try_log(heading(sftp,S#state.target),
+ "SSH Ref: ~p, Server: ~p~nCmd: ~p",
+ [SSHRef,ref(Srv,SSHRef),mod(Cmd)]),
{ssh_sftp:read_file(ref(Srv,SSHRef), File),S};
handle_msg({write_file,Srv,File,Iolist}=Cmd, S=#state{ssh_ref=SSHRef}) ->
- log(heading(sftp,S#state.target),
- "SSH Ref: ~p, Server: ~p~nCmd: ~p", [SSHRef,ref(Srv,SSHRef),mod(Cmd)]),
+ try_log(heading(sftp,S#state.target),
+ "SSH Ref: ~p, Server: ~p~nCmd: ~p",
+ [SSHRef,ref(Srv,SSHRef),mod(Cmd)]),
{ssh_sftp:write_file(ref(Srv,SSHRef), File, Iolist),S};
handle_msg({list_dir,Srv,Path}=Cmd, S=#state{ssh_ref=SSHRef}) ->
- log(heading(sftp,S#state.target),
- "SSH Ref: ~p, Server: ~p~nCmd: ~p", [SSHRef,ref(Srv,SSHRef),mod(Cmd)]),
+ try_log(heading(sftp,S#state.target),
+ "SSH Ref: ~p, Server: ~p~nCmd: ~p",
+ [SSHRef,ref(Srv,SSHRef),mod(Cmd)]),
{ssh_sftp:list_dir(ref(Srv,SSHRef), Path),S};
handle_msg({open,Srv,File,Mode}=Cmd, S=#state{ssh_ref=SSHRef}) ->
- log(heading(sftp,S#state.target),
- "SSH Ref: ~p, Server: ~p~nCmd: ~p", [SSHRef,ref(Srv,SSHRef),mod(Cmd)]),
+ try_log(heading(sftp,S#state.target),
+ "SSH Ref: ~p, Server: ~p~nCmd: ~p",
+ [SSHRef,ref(Srv,SSHRef),mod(Cmd)]),
{ssh_sftp:open(ref(Srv,SSHRef), File, Mode),S};
handle_msg({opendir,Srv,Path}=Cmd, S=#state{ssh_ref=SSHRef}) ->
- log(heading(sftp,S#state.target),
- "SSH Ref: ~p, Server: ~p~nCmd: ~p", [SSHRef,ref(Srv,SSHRef),mod(Cmd)]),
+ try_log(heading(sftp,S#state.target),
+ "SSH Ref: ~p, Server: ~p~nCmd: ~p",
+ [SSHRef,ref(Srv,SSHRef),mod(Cmd)]),
{ssh_sftp:opendir(ref(Srv,SSHRef), Path),S};
handle_msg({close,Srv,Handle}=Cmd, S=#state{ssh_ref=SSHRef}) ->
- log(heading(sftp,S#state.target),
- "SSH Ref: ~p, Server: ~p~nCmd: ~p", [SSHRef,ref(Srv,SSHRef),mod(Cmd)]),
+ try_log(heading(sftp,S#state.target),
+ "SSH Ref: ~p, Server: ~p~nCmd: ~p",
+ [SSHRef,ref(Srv,SSHRef),mod(Cmd)]),
{ssh_sftp:close(ref(Srv,SSHRef), Handle),S};
handle_msg({read,Srv,Handle,Len}=Cmd, S=#state{ssh_ref=SSHRef}) ->
- log(heading(sftp,S#state.target),
- "SSH Ref: ~p, Server: ~p~nCmd: ~p", [SSHRef,ref(Srv,SSHRef),mod(Cmd)]),
+ try_log(heading(sftp,S#state.target),
+ "SSH Ref: ~p, Server: ~p~nCmd: ~p",
+ [SSHRef,ref(Srv,SSHRef),mod(Cmd)]),
{ssh_sftp:read(ref(Srv,SSHRef), Handle, Len),S};
handle_msg({pread,Srv,Handle,Position,Length}=Cmd, S=#state{ssh_ref=SSHRef}) ->
- log(heading(sftp,S#state.target),
- "SSH Ref: ~p, Server: ~p~nCmd: ~p", [SSHRef,ref(Srv,SSHRef),mod(Cmd)]),
+ try_log(heading(sftp,S#state.target),
+ "SSH Ref: ~p, Server: ~p~nCmd: ~p",
+ [SSHRef,ref(Srv,SSHRef),mod(Cmd)]),
{ssh_sftp:pread(ref(Srv,SSHRef),Handle,Position,Length),S};
handle_msg({aread,Srv,Handle,Len}=Cmd, S=#state{ssh_ref=SSHRef}) ->
- log(heading(sftp,S#state.target),
- "SSH Ref: ~p, Server: ~p~nCmd: ~p", [SSHRef,ref(Srv,SSHRef),mod(Cmd)]),
+ try_log(heading(sftp,S#state.target),
+ "SSH Ref: ~p, Server: ~p~nCmd: ~p",
+ [SSHRef,ref(Srv,SSHRef),mod(Cmd)]),
{ssh_sftp:aread(ref(Srv,SSHRef), Handle, Len),S};
handle_msg({apread,Srv,Handle,Position,Length}=Cmd, S=#state{ssh_ref=SSHRef}) ->
- log(heading(sftp,S#state.target),
- "SSH Ref: ~p, Server: ~p~nCmd: ~p", [SSHRef,ref(Srv,SSHRef),mod(Cmd)]),
+ try_log(heading(sftp,S#state.target),
+ "SSH Ref: ~p, Server: ~p~nCmd: ~p",
+ [SSHRef,ref(Srv,SSHRef),mod(Cmd)]),
{ssh_sftp:apread(ref(Srv,SSHRef), Handle, Position, Length),S};
handle_msg({write,Srv,Handle,Data}=Cmd, S=#state{ssh_ref=SSHRef}) ->
- log(heading(sftp,S#state.target),
- "SSH Ref: ~p, Server: ~p~nCmd: ~p", [SSHRef,ref(Srv,SSHRef),mod(Cmd)]),
+ try_log(heading(sftp,S#state.target),
+ "SSH Ref: ~p, Server: ~p~nCmd: ~p",
+ [SSHRef,ref(Srv,SSHRef),mod(Cmd)]),
{ssh_sftp:write(ref(Srv,SSHRef), Handle, Data),S};
handle_msg({pwrite,Srv,Handle,Position,Data}=Cmd, S=#state{ssh_ref=SSHRef}) ->
- log(heading(sftp,S#state.target),
- "SSH Ref: ~p, Server: ~p~nCmd: ~p", [SSHRef,ref(Srv,SSHRef),mod(Cmd)]),
+ try_log(heading(sftp,S#state.target),
+ "SSH Ref: ~p, Server: ~p~nCmd: ~p",
+ [SSHRef,ref(Srv,SSHRef),mod(Cmd)]),
{ssh_sftp:pwrite(ref(Srv,SSHRef), Handle, Position, Data),S};
handle_msg({awrite,Srv,Handle,Data}=Cmd, S=#state{ssh_ref=SSHRef}) ->
- log(heading(sftp,S#state.target),
- "SSH Ref: ~p, Server: ~p~nCmd: ~p", [SSHRef,ref(Srv,SSHRef),mod(Cmd)]),
+ try_log(heading(sftp,S#state.target),
+ "SSH Ref: ~p, Server: ~p~nCmd: ~p",
+ [SSHRef,ref(Srv,SSHRef),mod(Cmd)]),
{ssh_sftp:awrite(ref(Srv,SSHRef), Handle, Data),S};
handle_msg({apwrite,Srv,Handle,Position,Data}=Cmd, S=#state{ssh_ref=SSHRef}) ->
- log(heading(sftp,S#state.target),
- "SSH Ref: ~p, Server: ~p~nCmd: ~p", [SSHRef,ref(Srv,SSHRef),mod(Cmd)]),
+ try_log(heading(sftp,S#state.target),
+ "SSH Ref: ~p, Server: ~p~nCmd: ~p",
+ [SSHRef,ref(Srv,SSHRef),mod(Cmd)]),
{ssh_sftp:apwrite(ref(Srv,SSHRef), Handle, Position, Data),S};
handle_msg({position,Srv,Handle,Location}=Cmd, S=#state{ssh_ref=SSHRef}) ->
- log(heading(sftp,S#state.target),
- "SSH Ref: ~p, Server: ~p~nCmd: ~p", [SSHRef,ref(Srv,SSHRef),mod(Cmd)]),
+ try_log(heading(sftp,S#state.target),
+ "SSH Ref: ~p, Server: ~p~nCmd: ~p",
+ [SSHRef,ref(Srv,SSHRef),mod(Cmd)]),
{ssh_sftp:position(ref(Srv,SSHRef), Handle, Location),S};
handle_msg({read_file_info,Srv,Name}=Cmd, S=#state{ssh_ref=SSHRef}) ->
- log(heading(sftp,S#state.target),
- "SSH Ref: ~p, Server: ~p~nCmd: ~p", [SSHRef,ref(Srv,SSHRef),mod(Cmd)]),
+ try_log(heading(sftp,S#state.target),
+ "SSH Ref: ~p, Server: ~p~nCmd: ~p",
+ [SSHRef,ref(Srv,SSHRef),mod(Cmd)]),
{ssh_sftp:read_file_info(ref(Srv,SSHRef), Name),S};
handle_msg({get_file_info,Srv,Handle}=Cmd, S=#state{ssh_ref=SSHRef}) ->
- log(heading(sftp,S#state.target),
- "SSH Ref: ~p, Server: ~p~nCmd: ~p", [SSHRef,ref(Srv,SSHRef),mod(Cmd)]),
+ try_log(heading(sftp,S#state.target),
+ "SSH Ref: ~p, Server: ~p~nCmd: ~p",
+ [SSHRef,ref(Srv,SSHRef),mod(Cmd)]),
{ssh_sftp:get_file_info(ref(Srv,SSHRef), Handle),S};
handle_msg({read_link_info,Srv,Name}=Cmd, S=#state{ssh_ref=SSHRef}) ->
- log(heading(sftp,S#state.target),
- "SSH Ref: ~p, Server: ~p~nCmd: ~p", [SSHRef,ref(Srv,SSHRef),mod(Cmd)]),
+ try_log(heading(sftp,S#state.target),
+ "SSH Ref: ~p, Server: ~p~nCmd: ~p",
+ [SSHRef,ref(Srv,SSHRef),mod(Cmd)]),
{ssh_sftp:read_link_info(ref(Srv,SSHRef), Name),S};
handle_msg({write_file_info,Srv,Name,Info}=Cmd, S=#state{ssh_ref=SSHRef}) ->
- log(heading(sftp,S#state.target),
- "SSH Ref: ~p, Server: ~p~nCmd: ~p", [SSHRef,ref(Srv,SSHRef),mod(Cmd)]),
+ try_log(heading(sftp,S#state.target),
+ "SSH Ref: ~p, Server: ~p~nCmd: ~p",
+ [SSHRef,ref(Srv,SSHRef),mod(Cmd)]),
{ssh_sftp:write_file_info(ref(Srv,SSHRef), Name, Info),S};
handle_msg({read_link,Srv,Name}=Cmd, S=#state{ssh_ref=SSHRef}) ->
- log(heading(sftp,S#state.target),
- "SSH Ref: ~p, Server: ~p~nCmd: ~p", [SSHRef,ref(Srv,SSHRef),mod(Cmd)]),
+ try_log(heading(sftp,S#state.target),
+ "SSH Ref: ~p, Server: ~p~nCmd: ~p",
+ [SSHRef,ref(Srv,SSHRef),mod(Cmd)]),
{ssh_sftp:read_link(ref(Srv,SSHRef), Name),S};
handle_msg({make_symlink,Srv,Name,Target}=Cmd, S=#state{ssh_ref=SSHRef}) ->
- log(heading(sftp,S#state.target),
- "SSH Ref: ~p, Server: ~p~nCmd: ~p", [SSHRef,ref(Srv,SSHRef),mod(Cmd)]),
+ try_log(heading(sftp,S#state.target),
+ "SSH Ref: ~p, Server: ~p~nCmd: ~p",
+ [SSHRef,ref(Srv,SSHRef),mod(Cmd)]),
{ssh_sftp:make_symlink(ref(Srv,SSHRef), Name, Target),S};
handle_msg({rename,Srv,OldName,NewName}=Cmd, S=#state{ssh_ref=SSHRef}) ->
- log(heading(sftp,S#state.target),
- "SSH Ref: ~p, Server: ~p~nCmd: ~p", [SSHRef,ref(Srv,SSHRef),mod(Cmd)]),
+ try_log(heading(sftp,S#state.target),
+ "SSH Ref: ~p, Server: ~p~nCmd: ~p",
+ [SSHRef,ref(Srv,SSHRef),mod(Cmd)]),
{ssh_sftp:rename(ref(Srv,SSHRef), OldName, NewName),S};
handle_msg({delete,Srv,Name}=Cmd, S=#state{ssh_ref=SSHRef}) ->
- log(heading(sftp,S#state.target),
- "SSH Ref: ~p, Server: ~p~nCmd: ~p", [SSHRef,ref(Srv,SSHRef),mod(Cmd)]),
+ try_log(heading(sftp,S#state.target),
+ "SSH Ref: ~p, Server: ~p~nCmd: ~p",
+ [SSHRef,ref(Srv,SSHRef),mod(Cmd)]),
{ssh_sftp:delete(ref(Srv,SSHRef), Name),S};
handle_msg({make_dir,Srv,Name}=Cmd, S=#state{ssh_ref=SSHRef}) ->
- log(heading(sftp,S#state.target),
- "SSH Ref: ~p, Server: ~p~nCmd: ~p", [SSHRef,ref(Srv,SSHRef),mod(Cmd)]),
+ try_log(heading(sftp,S#state.target),
+ "SSH Ref: ~p, Server: ~p~nCmd: ~p",
+ [SSHRef,ref(Srv,SSHRef),mod(Cmd)]),
{ssh_sftp:make_dir(ref(Srv,SSHRef), Name),S};
handle_msg({del_dir,Srv,Name}=Cmd, S=#state{ssh_ref=SSHRef}) ->
- log(heading(sftp,S#state.target),
- "SSH Ref: ~p, Server: ~p~nCmd: ~p", [SSHRef,ref(Srv,SSHRef),mod(Cmd)]),
+ try_log(heading(sftp,S#state.target),
+ "SSH Ref: ~p, Server: ~p~nCmd: ~p",
+ [SSHRef,ref(Srv,SSHRef),mod(Cmd)]),
{ssh_sftp:del_dir(ref(Srv,SSHRef), Name),S}.
%% @hidden
@@ -1197,12 +1227,12 @@ close(SSHRef) ->
terminate(SSHRef, State) ->
case State#state.conn_type of
ssh ->
- log(heading(disconnect_ssh,State#state.target),
- "SSH Ref: ~p",[SSHRef]),
+ try_log(heading(disconnect_ssh,State#state.target),
+ "SSH Ref: ~p",[SSHRef], 5000),
ssh:close(SSHRef);
sftp ->
- log(heading(disconnect_sftp,State#state.target),
- "SFTP Ref: ~p",[SSHRef]),
+ try_log(heading(disconnect_sftp,State#state.target),
+ "SFTP Ref: ~p",[SSHRef], 5000),
ssh_sftp:stop_channel(SSHRef)
end.
@@ -1217,7 +1247,7 @@ do_recv_response(SSH, Chn, Data, End, Timeout) ->
{ssh_cm, SSH, {open,Chn,RemoteChn,{session}}} ->
debug("RECVD open"),
{ok,{open,Chn,RemoteChn,{session}}};
-
+
{ssh_cm, SSH, {closed,Chn}} ->
ssh_connection:close(SSH, Chn),
debug("CLSD~n~p ~p", [SSH,Chn]),
@@ -1245,38 +1275,38 @@ do_recv_response(SSH, Chn, Data, End, Timeout) ->
{ssh_cm, SSH, {exit_signal,Chn,Signal,Err,_Lang}} ->
debug("RECVD exit_signal~n~p ~p~n~p ~p", [SSH,Chn,Signal,Err]),
do_recv_response(SSH, Chn, Data, End, Timeout);
-%% {ok,{exit_signal,Chn,Signal,Err,_Lang}};
+ %% {ok,{exit_signal,Chn,Signal,Err,_Lang}};
{ssh_cm, SSH, {exit_status,Chn,Status}} ->
debug("RECVD exit_status~n~p ~p~n~p", [SSH,Chn,Status]),
do_recv_response(SSH, Chn, Data, End, Timeout);
-%% {ok,{exit_status,Chn,_Status}};
+ %% {ok,{exit_status,Chn,_Status}};
-%% --- INTERACTIVE MESSAGES - NOT HANDLED ---
-%%
-%% {ssh_cm, SSH, {subsystem,Chn,WantReply,Name}} ->
-%% debug("RECVD SUBS WNTRPLY~n~p ~p~n~p~n~p",
-%% [SSH,Chn,WantReply]),
-%% ssh_connection:reply_request(SSH, WantReply, success, Chn),
-%% do_recv_response(SSH, Chn, Data, End, Timeout);
-
-%% {ssh_cm, SSH, {shell,WantReply}} ->
-%% debug("RECVD SHELL WNTRPLY~n~p ~p~n~p~n~p",
-%% [SSH,Chn,WantReply]),
-%% ssh_connection:reply_request(SSH, WantReply, success, Chn),
-%% do_recv_response(SSH,Chn,Data,End,Timeout);
-
-%% {ssh_cm, SSH, {pty,Chn,WantReply,Pty}} ->
-%% debug("RECVD PTY WNTRPLY~n~p ~p~n~p~n~p",
-%% [SSH,Chn,WantReply,Pty]),
-%% ssh_connection:reply_request(SSH, WantReply, success, Chn),
-%% do_recv_response(SSH, Chn, Data, End, Timeout);
-
-%% {ssh_cm, SSH, WCh={window_change,_Chn,_Width,_Height,_PixWidth,_PixHeight}} ->
-%% debug("RECVD WINCH"),
-%% {ok,WCh};
-
+ %% --- INTERACTIVE MESSAGES - NOT HANDLED ---
+ %%
+ %% {ssh_cm, SSH, {subsystem,Chn,WantReply,Name}} ->
+ %% debug("RECVD SUBS WNTRPLY~n~p ~p~n~p~n~p",
+ %% [SSH,Chn,WantReply]),
+ %% ssh_connection:reply_request(SSH, WantReply, success, Chn),
+ %% do_recv_response(SSH, Chn, Data, End, Timeout);
+
+ %% {ssh_cm, SSH, {shell,WantReply}} ->
+ %% debug("RECVD SHELL WNTRPLY~n~p ~p~n~p~n~p",
+ %% [SSH,Chn,WantReply]),
+ %% ssh_connection:reply_request(SSH, WantReply, success, Chn),
+ %% do_recv_response(SSH,Chn,Data,End,Timeout);
+
+ %% {ssh_cm, SSH, {pty,Chn,WantReply,Pty}} ->
+ %% debug("RECVD PTY WNTRPLY~n~p ~p~n~p~n~p",
+ %% [SSH,Chn,WantReply,Pty]),
+ %% ssh_connection:reply_request(SSH, WantReply, success, Chn),
+ %% do_recv_response(SSH, Chn, Data, End, Timeout);
+
+ %% {ssh_cm, SSH, WCh={window_change,_Chn,_Width,_Height,_PixWidth,_PixHeight}} ->
+ %% debug("RECVD WINCH"),
+ %% {ok,WCh};
+
Other ->
debug("UNEXPECTED MESSAGE~n~p ~p~n~p", [SSH,Chn,Other]),
do_recv_response(SSH, Chn, Data, End, Timeout)
@@ -1307,9 +1337,12 @@ get_handle(SSH) ->
%%%-----------------------------------------------------------------
%%%
call(SSH, Msg) ->
+ call(SSH, Msg, infinity).
+
+call(SSH, Msg, Timeout) ->
case get_handle(SSH) of
{ok,Pid} ->
- ct_gen_conn:call(Pid, Msg);
+ ct_gen_conn:call(Pid, Msg, Timeout);
Error ->
Error
end.
@@ -1318,13 +1351,13 @@ call(SSH, Msg) ->
%%%
ref(sftp, SSHRef) -> SSHRef;
ref(Server, _) -> Server.
-
+
%%%-----------------------------------------------------------------
%%%
mod(Cmd) ->
[Op,_Server|Args] = tuple_to_list(Cmd),
list_to_tuple([Op|Args]).
-
+
%%%-----------------------------------------------------------------
%%%
heading(Function, Ref) ->
@@ -1335,6 +1368,20 @@ heading(Function, Ref) ->
log(Heading, Str, Args) ->
ct_gen_conn:log(Heading, Str, Args).
+%%%-----------------------------------------------------------------
+%%%
+try_log(Heading, Str, Args) ->
+ try_log(Heading, Str, Args, infinity).
+
+try_log(Heading, Str, Args, Timeout) ->
+ case ct_util:is_silenced(ssh, Timeout) of
+ true ->
+ ok;
+ false ->
+ ct_gen_conn:log(Heading, Str, Args);
+ _Error ->
+ ok
+ end.
%%%-----------------------------------------------------------------
%%%
@@ -1342,5 +1389,5 @@ debug(Str) ->
debug(Str, []).
debug(_Str, _Args) ->
-%% io:format("~n--- ct_ssh debug ---~n" ++ _Str ++ "~n", _Args),
+ %% io:format("~n--- ct_ssh debug ---~n" ++ _Str ++ "~n", _Args),
ok.
diff --git a/lib/common_test/src/ct_testspec.erl b/lib/common_test/src/ct_testspec.erl
index 4c05f57520..7759aca16b 100644
--- a/lib/common_test/src/ct_testspec.erl
+++ b/lib/common_test/src/ct_testspec.erl
@@ -29,6 +29,8 @@
-include("ct_util.hrl").
+-define(testspec_fields, record_info(fields, testspec)).
+
%%%------------------------------------------------------------------
%%% NOTE:
%%% Multiple testspecs may be used as input with the result that
@@ -46,7 +48,8 @@
%%% Version 1 - extract and return all tests and skips for Node
%%% (incl all_nodes)
%%%-------------------------------------------------------------------
-prepare_tests(TestSpec,Node) when is_record(TestSpec,testspec), is_atom(Node) ->
+prepare_tests(TestSpec,Node) when is_record(TestSpec,testspec),
+ is_atom(Node) ->
case lists:keysearch(Node,1,prepare_tests(TestSpec)) of
{value,{Node,Run,Skip}} ->
{Run,Skip};
@@ -249,22 +252,23 @@ collect_tests_from_file1([Spec|Specs],TestSpec,Relaxed) ->
SpecDir = filename:dirname(filename:absname(Spec)),
case file:consult(Spec) of
{ok,Terms} ->
- TestSpec1 = collect_tests(Terms,
- TestSpec#testspec{spec_dir=SpecDir},
- Relaxed),
- collect_tests_from_file1(Specs,TestSpec1,Relaxed);
+ case collect_tests(Terms,
+ TestSpec#testspec{spec_dir=SpecDir},
+ Relaxed) of
+ TS = #testspec{tests=Tests, logdir=LogDirs} when Specs == [] ->
+ LogDirs1 = lists:delete(".",LogDirs) ++ ["."],
+ TS#testspec{tests=lists:flatten(Tests), logdir=LogDirs1};
+ TS = #testspec{alias = As, nodes = Ns} ->
+ TS1 = TS#testspec{alias = lists:reverse(As),
+ nodes = lists:reverse(Ns)},
+ collect_tests_from_file1(Specs,TS1,Relaxed)
+ end;
{error,Reason} ->
ReasonStr =
lists:flatten(io_lib:format("~s",
[file:format_error(Reason)])),
throw({error,{Spec,ReasonStr}})
- end;
-collect_tests_from_file1([],TS=#testspec{config=Cfgs,event_handler=EvHs,
- include=Incl,tests=Tests},_) ->
- TS#testspec{config=lists:reverse(Cfgs),
- event_handler=lists:reverse(EvHs),
- include=lists:reverse(Incl),
- tests=lists:flatten(Tests)}.
+ end.
collect_tests_from_list(Terms,Relaxed) ->
collect_tests_from_list(Terms,[node()],Relaxed).
@@ -278,30 +282,163 @@ collect_tests_from_list(Terms,Nodes,Relaxed) when is_list(Nodes) ->
E = {error,_} ->
E;
TS ->
- #testspec{config=Cfgs,event_handler=EvHs,include=Incl,tests=Tests} = TS,
- TS#testspec{config=lists:reverse(Cfgs),
- event_handler=lists:reverse(EvHs),
- include=lists:reverse(Incl),
- tests=lists:flatten(Tests)}
+ #testspec{tests=Tests, logdir=LogDirs} = TS,
+ LogDirs1 = lists:delete(".",LogDirs) ++ ["."],
+ TS#testspec{tests=lists:flatten(Tests), logdir=LogDirs1}
end.
collect_tests(Terms,TestSpec,Relaxed) ->
put(relaxed,Relaxed),
- TestSpec1 = get_global(Terms,TestSpec),
- TestSpec2 = get_all_nodes(Terms,TestSpec1),
- {Terms2, TestSpec3} = filter_init_terms(Terms, [], TestSpec2),
+ Terms1 = replace_names(Terms),
+ TestSpec1 = get_global(Terms1,TestSpec),
+ TestSpec2 = get_all_nodes(Terms1,TestSpec1),
+ {Terms2, TestSpec3} = filter_init_terms(Terms1, [], TestSpec2),
add_tests(Terms2,TestSpec3).
-get_global([{merge_tests, Bool} | Ts], Spec) ->
- get_global(Ts,Spec#testspec{ merge_tests = Bool });
+%% replace names (atoms) in the testspec matching those in 'define' terms by
+%% searching recursively through tuples and lists
+replace_names(Terms) ->
+ Defs =
+ lists:flatmap(fun(Def={define,Name,_Replacement}) ->
+ %% check that name follows convention
+ if not is_atom(Name) ->
+ throw({illegal_name_in_testspec,Name});
+ true ->
+ [First|_] = atom_to_list(Name),
+ if ((First == $?) or (First == $$)
+ or (First == $_)
+ or ((First >= $A)
+ and (First =< $Z))) ->
+ [Def];
+ true ->
+ throw({illegal_name_in_testspec,
+ Name})
+ end
+ end;
+ (_) -> []
+ end, Terms),
+ DefProps = replace_names_in_defs(Defs,[]),
+ replace_names(Terms,[],DefProps).
+
+replace_names_in_defs([Def|Left],ModDefs) ->
+ [{define,Name,Replacement}] = replace_names([Def],[],ModDefs),
+ replace_names_in_defs(Left,[{Name,Replacement}|ModDefs]);
+replace_names_in_defs([],ModDefs) ->
+ ModDefs.
+
+replace_names([Term|Ts],Modified,Defs) when is_tuple(Term) ->
+ [TypeTag|Data] = tuple_to_list(Term),
+ Term1 = list_to_tuple([TypeTag|replace_names_in_elems(Data,[],Defs)]),
+ replace_names(Ts,[Term1|Modified],Defs);
+replace_names([Term|Ts],Modified,Defs) when is_atom(Term) ->
+ case proplists:get_value(Term,Defs) of
+ undefined ->
+ replace_names(Ts,[Term|Modified],Defs);
+ Replacement ->
+ replace_names(Ts,[Replacement|Modified],Defs)
+ end;
+replace_names([Term=[Ch|_]|Ts],Modified,Defs) when is_integer(Ch) ->
+ %% Term *could* be a string, attempt to search through it
+ Term1 = replace_names_in_string(Term,Defs),
+ replace_names(Ts,[Term1|Modified],Defs);
+replace_names([Term|Ts],Modified,Defs) ->
+ replace_names(Ts,[Term|Modified],Defs);
+replace_names([],Modified,_Defs) ->
+ lists:reverse(Modified).
+
+replace_names_in_elems([Elem|Es],Modified,Defs) when is_tuple(Elem) ->
+ Elem1 = list_to_tuple(replace_names_in_elems(tuple_to_list(Elem),[],Defs)),
+ replace_names_in_elems(Es,[Elem1|Modified],Defs);
+replace_names_in_elems([Elem|Es],Modified,Defs) when is_atom(Elem) ->
+ case proplists:get_value(Elem,Defs) of
+ undefined ->
+ %% if Term is a node name, check it for replacements as well
+ Elem1 = replace_names_in_node(Elem,Defs),
+ replace_names_in_elems(Es,[Elem1|Modified],Defs);
+ Replacement ->
+ replace_names_in_elems(Es,[Replacement|Modified],Defs)
+ end;
+replace_names_in_elems([Elem=[Ch|_]|Es],Modified,Defs) when is_integer(Ch) ->
+ %% Term *could* be a string, attempt to search through it
+ case replace_names_in_string(Elem,Defs) of
+ Elem ->
+ List = replace_names_in_elems(Elem,[],Defs),
+ replace_names_in_elems(Es,[List|Modified],Defs);
+ Elem1 ->
+ replace_names_in_elems(Es,[Elem1|Modified],Defs)
+ end;
+replace_names_in_elems([Elem|Es],Modified,Defs) when is_list(Elem) ->
+ List = replace_names_in_elems(Elem,[],Defs),
+ replace_names_in_elems(Es,[List|Modified],Defs);
+replace_names_in_elems([Elem|Es],Modified,Defs) ->
+ replace_names_in_elems(Es,[Elem|Modified],Defs);
+replace_names_in_elems([],Modified,_Defs) ->
+ lists:reverse(Modified).
+
+replace_names_in_string(Term,Defs=[{Name,Replacement=[Ch|_]}|Ds])
+ when is_integer(Ch) ->
+ try re:replace(Term,[$'|atom_to_list(Name)]++"'",
+ Replacement,[{return,list}]) of
+ Term -> % no match, proceed
+ replace_names_in_string(Term,Ds);
+ Term1 ->
+ replace_names_in_string(Term1,Defs)
+ catch
+ _:_ -> Term % Term is not a string
+ end;
+replace_names_in_string(Term,[_|Ds]) ->
+ replace_names_in_string(Term,Ds);
+replace_names_in_string(Term,[]) ->
+ Term.
+
+replace_names_in_node(Node,Defs) ->
+ String = atom_to_list(Node),
+ case lists:member($@,String) of
+ true ->
+ list_to_atom(replace_names_in_node1(String,Defs));
+ false ->
+ Node
+ end.
+
+replace_names_in_node1(NodeStr,Defs=[{Name,Replacement}|Ds]) ->
+ ReplStr = case Replacement of
+ [Ch|_] when is_integer(Ch) -> Replacement;
+ _ when is_atom(Replacement) -> atom_to_list(Replacement);
+ _ -> false
+ end,
+ if ReplStr == false ->
+ replace_names_in_node1(NodeStr,Ds);
+ true ->
+ case re:replace(NodeStr,atom_to_list(Name),
+ ReplStr,[{return,list}]) of
+ NodeStr -> % no match, proceed
+ replace_names_in_node1(NodeStr,Ds);
+ NodeStr1 ->
+ replace_names_in_node1(NodeStr1,Defs)
+ end
+ end;
+replace_names_in_node1(NodeStr,[]) ->
+ NodeStr.
+
+
+%% global terms that will be used for analysing all other terms in the spec
+get_global([{merge_tests,Bool} | Ts], Spec) ->
+ get_global(Ts,Spec#testspec{merge_tests=Bool});
+
+%% the 'define' term replaces the 'alias' and 'node' terms, but we need to keep
+%% the latter two for backwards compatibility...
get_global([{alias,Ref,Dir}|Ts],Spec=#testspec{alias=Refs}) ->
get_global(Ts,Spec#testspec{alias=[{Ref,get_absdir(Dir,Spec)}|Refs]});
get_global([{node,Ref,Node}|Ts],Spec=#testspec{nodes=Refs}) ->
- get_global(Ts,Spec#testspec{nodes=[{Ref,Node}|lists:keydelete(Node,2,Refs)]});
-get_global([_|Ts],Spec) -> get_global(Ts,Spec);
-get_global([],Spec) -> Spec.
+ get_global(Ts,Spec#testspec{nodes=[{Ref,Node} |
+ lists:keydelete(Node,2,Refs)]});
-get_absfile(Callback, FullName,#testspec{spec_dir=SpecDir}) ->
+get_global([_|Ts],Spec) ->
+ get_global(Ts,Spec);
+get_global([],Spec=#testspec{nodes=Ns, alias=As}) ->
+ Spec#testspec{nodes=lists:reverse(Ns), alias=lists:reverse(As)}.
+
+get_absfile(Callback,FullName,#testspec{spec_dir=SpecDir}) ->
% we need to temporary switch to new cwd here, because
% otherwise config files cannot be found
{ok, OldWd} = file:get_cwd(),
@@ -329,29 +466,45 @@ get_absfile(FullName,#testspec{spec_dir=SpecDir}) ->
get_absdir(Dir,#testspec{spec_dir=SpecDir}) ->
get_absname(Dir,SpecDir).
-get_absname(TestDir,SpecDir) ->
- AbsName = filename:absname(TestDir,SpecDir),
- TestDirName = filename:basename(AbsName),
- Path = filename:dirname(AbsName),
- TopDir = filename:basename(Path),
- Path1 =
- case TopDir of
- "." ->
- [_|Rev] = lists:reverse(filename:split(Path)),
- filename:join(lists:reverse(Rev));
- ".." ->
- [_,_|Rev] = lists:reverse(filename:split(Path)),
- filename:join(lists:reverse(Rev));
- _ ->
- Path
- end,
- filename:join(Path1,TestDirName).
+get_absname(Dir,SpecDir) ->
+ AbsName = filename:absname(Dir,SpecDir),
+ shorten_path(AbsName,SpecDir).
+
+shorten_path(Path,SpecDir) ->
+ case shorten_split_path(filename:split(Path),[]) of
+ [] ->
+ [Root|_] = filename:split(SpecDir),
+ Root;
+ Short ->
+ filename:join(Short)
+ end.
+
+shorten_split_path([".."|Path],SoFar) ->
+ shorten_split_path(Path,tl(SoFar));
+shorten_split_path(["."|Path],SoFar) ->
+ shorten_split_path(Path,SoFar);
+shorten_split_path([Dir|Path],SoFar) ->
+ shorten_split_path(Path,[Dir|SoFar]);
+shorten_split_path([],SoFar) ->
+ lists:reverse(SoFar).
%% go through all tests and register all nodes found
get_all_nodes([{suites,Nodes,_,_}|Ts],Spec) when is_list(Nodes) ->
get_all_nodes(Ts,save_nodes(Nodes,Spec));
get_all_nodes([{suites,Node,_,_}|Ts],Spec) ->
get_all_nodes(Ts,save_nodes([Node],Spec));
+get_all_nodes([{groups,[Char|_],_,_,_}|Ts],Spec) when is_integer(Char) ->
+ get_all_nodes(Ts,Spec);
+get_all_nodes([{groups,Nodes,_,_,_}|Ts],Spec) when is_list(Nodes) ->
+ get_all_nodes(Ts,save_nodes(Nodes,Spec));
+get_all_nodes([{groups,Nodes,_,_,_,_}|Ts],Spec) when is_list(Nodes) ->
+ get_all_nodes(Ts,save_nodes(Nodes,Spec));
+get_all_nodes([{groups,_,_,_,{cases,_}}|Ts],Spec) ->
+ get_all_nodes(Ts,Spec);
+get_all_nodes([{groups,Node,_,_,_}|Ts],Spec) ->
+ get_all_nodes(Ts,save_nodes([Node],Spec));
+get_all_nodes([{groups,Node,_,_,_,_}|Ts],Spec) ->
+ get_all_nodes(Ts,save_nodes([Node],Spec));
get_all_nodes([{cases,Nodes,_,_,_}|Ts],Spec) when is_list(Nodes) ->
get_all_nodes(Ts,save_nodes(Nodes,Spec));
get_all_nodes([{cases,Node,_,_,_}|Ts],Spec) ->
@@ -360,74 +513,93 @@ get_all_nodes([{skip_suites,Nodes,_,_,_}|Ts],Spec) when is_list(Nodes) ->
get_all_nodes(Ts,save_nodes(Nodes,Spec));
get_all_nodes([{skip_suites,Node,_,_,_}|Ts],Spec) ->
get_all_nodes(Ts,save_nodes([Node],Spec));
+get_all_nodes([{skip_groups,[Char|_],_,_,_,_}|Ts],Spec) when is_integer(Char) ->
+ get_all_nodes(Ts,Spec);
+get_all_nodes([{skip_groups,Nodes,_,_,_,_}|Ts],Spec) when is_list(Nodes) ->
+ get_all_nodes(Ts,save_nodes(Nodes,Spec));
+get_all_nodes([{skip_groups,Node,_,_,_,_}|Ts],Spec) ->
+ get_all_nodes(Ts,save_nodes([Node],Spec));
+get_all_nodes([{skip_groups,Nodes,_,_,_,_,_}|Ts],Spec) when is_list(Nodes) ->
+ get_all_nodes(Ts,save_nodes(Nodes,Spec));
+get_all_nodes([{skip_groups,Node,_,_,_,_,_}|Ts],Spec) ->
+ get_all_nodes(Ts,save_nodes([Node],Spec));
get_all_nodes([{skip_cases,Nodes,_,_,_,_}|Ts],Spec) when is_list(Nodes) ->
get_all_nodes(Ts,save_nodes(Nodes,Spec));
get_all_nodes([{skip_cases,Node,_,_,_,_}|Ts],Spec) ->
get_all_nodes(Ts,save_nodes([Node],Spec));
-get_all_nodes([_|Ts],Spec) ->
+get_all_nodes([_Other|Ts],Spec) ->
get_all_nodes(Ts,Spec);
get_all_nodes([],Spec) ->
Spec.
-filter_init_terms([{init, InitOptions}|Ts], NewTerms, Spec)->
- filter_init_terms([{init, list_nodes(Spec), InitOptions}|Ts], NewTerms, Spec);
-filter_init_terms([{init, NodeRef, InitOptions}|Ts], NewTerms, Spec)
- when is_atom(NodeRef)->
- filter_init_terms([{init, [NodeRef], InitOptions}|Ts], NewTerms, Spec);
-filter_init_terms([{init, NodeRefs, InitOption}|Ts], NewTerms, Spec) when is_tuple(InitOption) ->
- filter_init_terms([{init, NodeRefs, [InitOption]}|Ts], NewTerms, Spec);
-filter_init_terms([{init, [NodeRef|NodeRefs], InitOptions}|Ts], NewTerms, Spec=#testspec{init=InitData})->
- NodeStartOptions = case lists:keyfind(node_start, 1, InitOptions) of
- {node_start, NSOptions}->
- case lists:keyfind(callback_module, 1, NSOptions) of
- {callback_module, _Callback}->
- NSOptions;
- false->
- [{callback_module, ct_slave}|NSOptions]
- end;
- false->
- []
- end,
- EvalTerms = case lists:keyfind(eval, 1, InitOptions) of
- {eval, MFA} when is_tuple(MFA)->
- [MFA];
- {eval, MFAs} when is_list(MFAs)->
- MFAs;
- false->
- []
- end,
+filter_init_terms([{init,InitOptions}|Ts],NewTerms,Spec) ->
+ filter_init_terms([{init,list_nodes(Spec),InitOptions}|Ts],
+ NewTerms,Spec);
+filter_init_terms([{init,all_nodes,InitOptions}|Ts],NewTerms,Spec) ->
+ filter_init_terms([{init,list_nodes(Spec),InitOptions}|Ts],
+ NewTerms,Spec);
+filter_init_terms([{init,NodeRef,InitOptions}|Ts],
+ NewTerms,Spec) when is_atom(NodeRef) ->
+ filter_init_terms([{init,[NodeRef],InitOptions}|Ts],NewTerms,Spec);
+filter_init_terms([{init,NodeRefs,InitOption}|Ts],
+ NewTerms,Spec) when is_tuple(InitOption) ->
+ filter_init_terms([{init,NodeRefs,[InitOption]}|Ts],NewTerms,Spec);
+filter_init_terms([{init,[NodeRef|NodeRefs],InitOptions}|Ts],
+ NewTerms,Spec=#testspec{init=InitData}) ->
+ NodeStartOptions =
+ case lists:keyfind(node_start,1,InitOptions) of
+ {node_start,NSOptions}->
+ case lists:keyfind(callback_module,1,NSOptions) of
+ {callback_module,_Callback}->
+ NSOptions;
+ false->
+ [{callback_module,ct_slave}|NSOptions]
+ end;
+ false->
+ []
+ end,
+ EvalTerms = case lists:keyfind(eval,1,InitOptions) of
+ {eval,MFA} when is_tuple(MFA) ->
+ [MFA];
+ {eval,MFAs} when is_list(MFAs) ->
+ MFAs;
+ false->
+ []
+ end,
Node = ref2node(NodeRef,Spec#testspec.nodes),
- InitData2 = add_option({node_start, NodeStartOptions}, Node, InitData, true),
- InitData3 = add_option({eval, EvalTerms}, Node, InitData2, false),
- filter_init_terms([{init, NodeRefs, InitOptions}|Ts], NewTerms, Spec#testspec{init=InitData3});
-filter_init_terms([{init, [], _}|Ts], NewTerms, Spec)->
- filter_init_terms(Ts, NewTerms, Spec);
-filter_init_terms([Term|Ts], NewTerms, Spec)->
- filter_init_terms(Ts, [Term|NewTerms], Spec);
-filter_init_terms([], NewTerms, Spec)->
- {lists:reverse(NewTerms), Spec}.
-
-add_option({Key, Value}, Node, List, WarnIfExists) when is_list(Value)->
- OldOptions = case lists:keyfind(Node, 1, List) of
- {Node, Options}->
+ InitData2 = add_option({node_start,NodeStartOptions},Node,InitData,true),
+ InitData3 = add_option({eval,EvalTerms},Node,InitData2,false),
+ filter_init_terms([{init,NodeRefs,InitOptions}|Ts],
+ NewTerms,Spec#testspec{init=InitData3});
+filter_init_terms([{init,[],_}|Ts],NewTerms,Spec) ->
+ filter_init_terms(Ts,NewTerms,Spec);
+filter_init_terms([Term|Ts],NewTerms,Spec) ->
+ filter_init_terms(Ts,[Term|NewTerms],Spec);
+filter_init_terms([],NewTerms,Spec) ->
+ {lists:reverse(NewTerms),Spec}.
+
+add_option({Key,Value},Node,List,WarnIfExists) when is_list(Value) ->
+ OldOptions = case lists:keyfind(Node,1,List) of
+ {Node,Options}->
Options;
false->
[]
end,
- NewOption = case lists:keyfind(Key, 1, OldOptions) of
- {Key, OldOption} when WarnIfExists, OldOption/=[]->
- io:format("There is an option ~w=~w already defined for node ~p, skipping new ~w~n",
- [Key, OldOption, Node, Value]),
+ NewOption = case lists:keyfind(Key,1,OldOptions) of
+ {Key,OldOption} when WarnIfExists,OldOption/=[]->
+ io:format("There is an option ~w=~w already "
+ "defined for node ~p, skipping new ~w~n",
+ [Key,OldOption,Node,Value]),
OldOption;
- {Key, OldOption}->
+ {Key,OldOption}->
OldOption ++ Value;
false->
Value
end,
- lists:keystore(Node, 1, List,
- {Node, lists:keystore(Key, 1, OldOptions, {Key, NewOption})});
-add_option({Key, Value}, Node, List, WarnIfExists)->
- add_option({Key, [Value]}, Node, List, WarnIfExists).
+ lists:keystore(Node,1,List,
+ {Node,lists:keystore(Key,1,OldOptions,{Key,NewOption})});
+add_option({Key,Value},Node,List,WarnIfExists) ->
+ add_option({Key,[Value]},Node,List,WarnIfExists).
save_nodes(Nodes,Spec=#testspec{nodes=NodeRefs}) ->
NodeRefs1 =
@@ -446,267 +618,18 @@ save_nodes(Nodes,Spec=#testspec{nodes=NodeRefs}) ->
end
end
end,NodeRefs,Nodes),
- Spec#testspec{nodes=NodeRefs1}.
+ Spec#testspec{nodes=NodeRefs1}.
list_nodes(#testspec{nodes=NodeRefs}) ->
lists:map(fun({_Ref,Node}) -> Node end, NodeRefs).
-
-%% ---------------------------------------------------------
-%% / \
-%% | When adding tests, remember to update valid_terms/0 also! |
-%% \ /
-%% ---------------------------------------------------------
-
-
-%% Associate a "global" logdir with all nodes
-%% except those with specific logdir, e.g:
-%% ["/tmp/logdir",{ct1@finwe,"/tmp/logdir2"}]
-%% means all nodes should write to /tmp/logdir
-%% except ct1@finwe that should use /tmp/logdir2.
-
-%% --- logdir ---
-add_tests([{logdir,all_nodes,Dir}|Ts],Spec) ->
- Dirs = Spec#testspec.logdir,
- Tests = [{logdir,N,get_absdir(Dir,Spec)} ||
- N <- list_nodes(Spec),
- lists:keymember(ref2node(N,Spec#testspec.nodes),
- 1,Dirs) == false],
- add_tests(Tests++Ts,Spec);
-add_tests([{logdir,Nodes,Dir}|Ts],Spec) when is_list(Nodes) ->
- Ts1 = separate(Nodes,logdir,[Dir],Ts,Spec#testspec.nodes),
- add_tests(Ts1,Spec);
-add_tests([{logdir,Node,Dir}|Ts],Spec) ->
- Dirs = Spec#testspec.logdir,
- Dirs1 = [{ref2node(Node,Spec#testspec.nodes),get_absdir(Dir,Spec)} |
- lists:keydelete(ref2node(Node,Spec#testspec.nodes),1,Dirs)],
- add_tests(Ts,Spec#testspec{logdir=Dirs1});
-add_tests([{logdir,Dir}|Ts],Spec) ->
- add_tests([{logdir,all_nodes,Dir}|Ts],Spec);
-
-%% --- logopts ---
-add_tests([{logopts,all_nodes,Opts}|Ts],Spec) ->
- LogOpts = Spec#testspec.logopts,
- Tests = [{logopts,N,Opts} ||
- N <- list_nodes(Spec),
- lists:keymember(ref2node(N,Spec#testspec.nodes),1,
- LogOpts) == false],
- add_tests(Tests++Ts,Spec);
-add_tests([{logopts,Nodes,Opts}|Ts],Spec) when is_list(Nodes) ->
- Ts1 = separate(Nodes,logopts,[Opts],Ts,Spec#testspec.nodes),
- add_tests(Ts1,Spec);
-add_tests([{logopts,Node,Opts}|Ts],Spec) ->
- LogOpts = Spec#testspec.logopts,
- LogOpts1 = [{ref2node(Node,Spec#testspec.nodes),Opts} |
- lists:keydelete(ref2node(Node,Spec#testspec.nodes),
- 1,LogOpts)],
- add_tests(Ts,Spec#testspec{logopts=LogOpts1});
-add_tests([{logopts,Opts}|Ts],Spec) ->
- add_tests([{logopts,all_nodes,Opts}|Ts],Spec);
-
-%% --- label ---
-add_tests([{label,all_nodes,Lbl}|Ts],Spec) ->
- Labels = Spec#testspec.label,
- Tests = [{label,N,Lbl} || N <- list_nodes(Spec),
- lists:keymember(ref2node(N,Spec#testspec.nodes),
- 1,Labels) == false],
- add_tests(Tests++Ts,Spec);
-add_tests([{label,Nodes,Lbl}|Ts],Spec) when is_list(Nodes) ->
- Ts1 = separate(Nodes,label,[Lbl],Ts,Spec#testspec.nodes),
- add_tests(Ts1,Spec);
-add_tests([{label,Node,Lbl}|Ts],Spec) ->
- Labels = Spec#testspec.label,
- Labels1 = [{ref2node(Node,Spec#testspec.nodes),Lbl} |
- lists:keydelete(ref2node(Node,Spec#testspec.nodes),1,Labels)],
- add_tests(Ts,Spec#testspec{label=Labels1});
-add_tests([{label,Lbl}|Ts],Spec) ->
- add_tests([{label,all_nodes,Lbl}|Ts],Spec);
-
-%% --- cover ---
-add_tests([{cover,all_nodes,File}|Ts],Spec) ->
- Tests = lists:map(fun(N) -> {cover,N,File} end, list_nodes(Spec)),
- add_tests(Tests++Ts,Spec);
-add_tests([{cover,Nodes,File}|Ts],Spec) when is_list(Nodes) ->
- Ts1 = separate(Nodes,cover,[File],Ts,Spec#testspec.nodes),
- add_tests(Ts1,Spec);
-add_tests([{cover,Node,File}|Ts],Spec) ->
- CoverFs = Spec#testspec.cover,
- CoverFs1 = [{ref2node(Node,Spec#testspec.nodes),get_absfile(File,Spec)} |
- lists:keydelete(ref2node(Node,Spec#testspec.nodes),1,CoverFs)],
- add_tests(Ts,Spec#testspec{cover=CoverFs1});
-add_tests([{cover,File}|Ts],Spec) ->
- add_tests([{cover,all_nodes,File}|Ts],Spec);
-
-%% --- multiply_timetraps ---
-add_tests([{multiply_timetraps,all_nodes,MT}|Ts],Spec) ->
- Tests = lists:map(fun(N) -> {multiply_timetraps,N,MT} end, list_nodes(Spec)),
- add_tests(Tests++Ts,Spec);
-add_tests([{multiply_timetraps,Nodes,MT}|Ts],Spec) when is_list(Nodes) ->
- Ts1 = separate(Nodes,multiply_timetraps,[MT],Ts,Spec#testspec.nodes),
- add_tests(Ts1,Spec);
-add_tests([{multiply_timetraps,Node,MT}|Ts],Spec) ->
- MTs = Spec#testspec.multiply_timetraps,
- MTs1 = [{ref2node(Node,Spec#testspec.nodes),MT} |
- lists:keydelete(ref2node(Node,Spec#testspec.nodes),1,MTs)],
- add_tests(Ts,Spec#testspec{multiply_timetraps=MTs1});
-add_tests([{multiply_timetraps,MT}|Ts],Spec) ->
- add_tests([{multiply_timetraps,all_nodes,MT}|Ts],Spec);
-
-%% --- scale_timetraps ---
-add_tests([{scale_timetraps,all_nodes,ST}|Ts],Spec) ->
- Tests = lists:map(fun(N) -> {scale_timetraps,N,ST} end, list_nodes(Spec)),
- add_tests(Tests++Ts,Spec);
-add_tests([{scale_timetraps,Nodes,ST}|Ts],Spec) when is_list(Nodes) ->
- Ts1 = separate(Nodes,scale_timetraps,[ST],Ts,Spec#testspec.nodes),
- add_tests(Ts1,Spec);
-add_tests([{scale_timetraps,Node,ST}|Ts],Spec) ->
- STs = Spec#testspec.scale_timetraps,
- STs1 = [{ref2node(Node,Spec#testspec.nodes),ST} |
- lists:keydelete(ref2node(Node,Spec#testspec.nodes),1,STs)],
- add_tests(Ts,Spec#testspec{scale_timetraps=STs1});
-add_tests([{scale_timetraps,ST}|Ts],Spec) ->
- add_tests([{scale_timetraps,all_nodes,ST}|Ts],Spec);
-
-%% --- create_priv_dir ---
-add_tests([{create_priv_dir,all_nodes,PD}|Ts],Spec) ->
- Tests = lists:map(fun(N) -> {create_priv_dir,N,PD} end, list_nodes(Spec)),
- add_tests(Tests++Ts,Spec);
-add_tests([{create_priv_dir,Nodes,PD}|Ts],Spec) when is_list(Nodes) ->
- Ts1 = separate(Nodes,create_priv_dir,[PD],Ts,Spec#testspec.nodes),
- add_tests(Ts1,Spec);
-add_tests([{create_priv_dir,Node,PD}|Ts],Spec) ->
- PDs = Spec#testspec.create_priv_dir,
- PDs1 = [{ref2node(Node,Spec#testspec.nodes),PD} |
- lists:keydelete(ref2node(Node,Spec#testspec.nodes),1,PDs)],
- add_tests(Ts,Spec#testspec{create_priv_dir=PDs1});
-add_tests([{create_priv_dir,PD}|Ts],Spec) ->
- add_tests([{create_priv_dir,all_nodes,PD}|Ts],Spec);
-
-%% --- config ---
-add_tests([{config,all_nodes,Files}|Ts],Spec) ->
- Tests = lists:map(fun(N) -> {config,N,Files} end, list_nodes(Spec)),
- add_tests(Tests++Ts,Spec);
-add_tests([{config,Nodes,Files}|Ts],Spec) when is_list(Nodes) ->
- Ts1 = separate(Nodes,config,[Files],Ts,Spec#testspec.nodes),
- add_tests(Ts1,Spec);
-add_tests([{config,Node,[F|Fs]}|Ts],Spec) when is_list(F) ->
- Cfgs = Spec#testspec.config,
- Node1 = ref2node(Node,Spec#testspec.nodes),
- add_tests([{config,Node,Fs}|Ts],
- Spec#testspec{config=[{Node1,get_absfile(F,Spec)}|Cfgs]});
-add_tests([{config,_Node,[]}|Ts],Spec) ->
- add_tests(Ts,Spec);
-add_tests([{config,Node,F}|Ts],Spec) ->
- add_tests([{config,Node,[F]}|Ts],Spec);
-add_tests([{config,Files}|Ts],Spec) ->
- add_tests([{config,all_nodes,Files}|Ts],Spec);
-
-
-%% --- userconfig ---
-add_tests([{userconfig,all_nodes,CBF}|Ts],Spec) ->
- Tests = lists:map(fun(N) -> {userconfig,N,CBF} end, list_nodes(Spec)),
- add_tests(Tests++Ts,Spec);
-add_tests([{userconfig,Nodes,CBF}|Ts],Spec) when is_list(Nodes) ->
- Ts1 = separate(Nodes,userconfig,[CBF],Ts,Spec#testspec.nodes),
- add_tests(Ts1,Spec);
-add_tests([{userconfig,Node,[{Callback, Config}|CBF]}|Ts],Spec) ->
- Cfgs = Spec#testspec.userconfig,
- Node1 = ref2node(Node,Spec#testspec.nodes),
- add_tests([{userconfig,Node,CBF}|Ts],
- Spec#testspec{userconfig=[{Node1,{Callback,
- get_absfile(Callback, Config ,Spec)}}|Cfgs]});
-add_tests([{userconfig,_Node,[]}|Ts],Spec) ->
- add_tests(Ts,Spec);
-add_tests([{userconfig,Node,CBF}|Ts],Spec) ->
- add_tests([{userconfig,Node,[CBF]}|Ts],Spec);
-add_tests([{userconfig,CBF}|Ts],Spec) ->
- add_tests([{userconfig,all_nodes,CBF}|Ts],Spec);
-
-%% --- event_handler ---
-add_tests([{event_handler,all_nodes,Hs}|Ts],Spec) ->
- Tests = lists:map(fun(N) -> {event_handler,N,Hs,[]} end, list_nodes(Spec)),
- add_tests(Tests++Ts,Spec);
-add_tests([{event_handler,all_nodes,Hs,Args}|Ts],Spec) when is_list(Args) ->
- Tests = lists:map(fun(N) -> {event_handler,N,Hs,Args} end, list_nodes(Spec)),
- add_tests(Tests++Ts,Spec);
-add_tests([{event_handler,Hs}|Ts],Spec) ->
- add_tests([{event_handler,all_nodes,Hs,[]}|Ts],Spec);
-add_tests([{event_handler,HsOrNodes,HsOrArgs}|Ts],Spec) ->
- case is_noderef(HsOrNodes,Spec#testspec.nodes) of
- true -> % HsOrNodes == Nodes, HsOrArgs == Hs
- case {HsOrNodes,HsOrArgs} of
- {Nodes,Hs} when is_list(Nodes) ->
- Ts1 = separate(Nodes,event_handler,[Hs,[]],Ts,
- Spec#testspec.nodes),
- add_tests(Ts1,Spec);
- {_Node,[]} ->
- add_tests(Ts,Spec);
- {Node,HOrHs} ->
- EvHs = Spec#testspec.event_handler,
- Node1 = ref2node(Node,Spec#testspec.nodes),
- case HOrHs of
- [H|Hs] when is_atom(H) ->
- add_tests([{event_handler,Node,Hs}|Ts],
- Spec#testspec{event_handler=[{Node1,H,[]}|EvHs]});
- H when is_atom(H) ->
- add_tests(Ts,Spec#testspec{event_handler=[{Node1,H,[]}|EvHs]})
- end
- end;
- false -> % HsOrNodes == Hs, HsOrArgs == Args
- add_tests([{event_handler,all_nodes,HsOrNodes,HsOrArgs}|Ts],Spec)
- end;
-add_tests([{event_handler,Nodes,Hs,Args}|Ts],Spec) when is_list(Nodes) ->
- Ts1 = separate(Nodes,event_handler,[Hs,Args],Ts,Spec#testspec.nodes),
- add_tests(Ts1,Spec);
-add_tests([{event_handler,Node,[H|Hs],Args}|Ts],Spec) when is_atom(H) ->
- EvHs = Spec#testspec.event_handler,
- Node1 = ref2node(Node,Spec#testspec.nodes),
- add_tests([{event_handler,Node,Hs,Args}|Ts],
- Spec#testspec{event_handler=[{Node1,H,Args}|EvHs]});
-add_tests([{event_handler,_Node,[],_Args}|Ts],Spec) ->
- add_tests(Ts,Spec);
-add_tests([{event_handler,Node,H,Args}|Ts],Spec) when is_atom(H) ->
- EvHs = Spec#testspec.event_handler,
- Node1 = ref2node(Node,Spec#testspec.nodes),
- add_tests(Ts,Spec#testspec{event_handler=[{Node1,H,Args}|EvHs]});
-
-%% --- ct_hooks --
-add_tests([{ct_hooks, all_nodes, Hooks} | Ts], Spec) ->
- Tests = [{ct_hooks,N,Hooks} || N <- list_nodes(Spec)],
- add_tests(Tests ++ Ts, Spec);
-add_tests([{ct_hooks, Node, [Hook|Hooks]}|Ts], Spec) ->
- SuiteCbs = Spec#testspec.ct_hooks,
- Node1 = ref2node(Node,Spec#testspec.nodes),
- add_tests([{ct_hooks, Node, Hooks} | Ts],
- Spec#testspec{ct_hooks = [{Node1,Hook} | SuiteCbs]});
-add_tests([{ct_hooks, _Node, []}|Ts], Spec) ->
- add_tests(Ts, Spec);
-add_tests([{ct_hooks, Hooks}|Ts], Spec) ->
- add_tests([{ct_hooks, all_nodes, Hooks}|Ts], Spec);
-
-%% -- enable_builtin_hooks --
-add_tests([{enable_builtin_hooks,Bool}|Ts],Spec) ->
- add_tests(Ts, Spec#testspec{ enable_builtin_hooks = Bool });
-
-%% --- include ---
-add_tests([{include,all_nodes,InclDirs}|Ts],Spec) ->
- Tests = lists:map(fun(N) -> {include,N,InclDirs} end, list_nodes(Spec)),
- add_tests(Tests++Ts,Spec);
-add_tests([{include,Nodes,InclDirs}|Ts],Spec) when is_list(Nodes) ->
- Ts1 = separate(Nodes,include,[InclDirs],Ts,Spec#testspec.nodes),
- add_tests(Ts1,Spec);
-add_tests([{include,Node,[D|Ds]}|Ts],Spec) when is_list(D) ->
- Dirs = Spec#testspec.include,
- Node1 = ref2node(Node,Spec#testspec.nodes),
- add_tests([{include,Node,Ds}|Ts],
- Spec#testspec{include=[{Node1,get_absdir(D,Spec)}|Dirs]});
-add_tests([{include,_Node,[]}|Ts],Spec) ->
- add_tests(Ts,Spec);
-add_tests([{include,Node,D}|Ts],Spec) ->
- add_tests([{include,Node,[D]}|Ts],Spec);
-add_tests([{include,InclDirs}|Ts],Spec) ->
- add_tests([{include,all_nodes,InclDirs}|Ts],Spec);
+%% -----------------------------------------------------
+%% / \
+%% | When adding test/config terms, remember to update |
+%% | valid_terms/0 also! |
+%% \ /
+%% -----------------------------------------------------
%% --- suites ---
add_tests([{suites,all_nodes,Dir,Ss}|Ts],Spec) ->
@@ -719,7 +642,7 @@ add_tests([{suites,Nodes,Dir,Ss}|Ts],Spec) when is_list(Nodes) ->
add_tests([{suites,Node,Dir,Ss}|Ts],Spec) ->
Tests = Spec#testspec.tests,
Tests1 = insert_suites(ref2node(Node,Spec#testspec.nodes),
- ref2dir(Dir,Spec#testspec.alias),
+ ref2dir(Dir,Spec),
Ss,Tests, Spec#testspec.merge_tests),
add_tests(Ts,Spec#testspec{tests=Tests1});
@@ -739,20 +662,22 @@ add_tests([{groups,Dir,Suite,Gs,{cases,TCs}}|Ts],Spec) ->
add_tests([{groups,Nodes,Dir,Suite,Gs}|Ts],Spec) when is_list(Nodes) ->
Ts1 = separate(Nodes,groups,[Dir,Suite,Gs],Ts,Spec#testspec.nodes),
add_tests(Ts1,Spec);
-add_tests([{groups,Nodes,Dir,Suite,Gs,{cases,TCs}}|Ts],Spec) when is_list(Nodes) ->
- Ts1 = separate(Nodes,groups,[Dir,Suite,Gs,{cases,TCs}],Ts,Spec#testspec.nodes),
+add_tests([{groups,Nodes,Dir,Suite,Gs,{cases,TCs}}|Ts],
+ Spec) when is_list(Nodes) ->
+ Ts1 = separate(Nodes,groups,[Dir,Suite,Gs,{cases,TCs}],Ts,
+ Spec#testspec.nodes),
add_tests(Ts1,Spec);
add_tests([{groups,Node,Dir,Suite,Gs}|Ts],Spec) ->
Tests = Spec#testspec.tests,
Tests1 = insert_groups(ref2node(Node,Spec#testspec.nodes),
- ref2dir(Dir,Spec#testspec.alias),
+ ref2dir(Dir,Spec),
Suite,Gs,all,Tests,
Spec#testspec.merge_tests),
add_tests(Ts,Spec#testspec{tests=Tests1});
add_tests([{groups,Node,Dir,Suite,Gs,{cases,TCs}}|Ts],Spec) ->
Tests = Spec#testspec.tests,
Tests1 = insert_groups(ref2node(Node,Spec#testspec.nodes),
- ref2dir(Dir,Spec#testspec.alias),
+ ref2dir(Dir,Spec),
Suite,Gs,TCs,Tests,
Spec#testspec.merge_tests),
add_tests(Ts,Spec#testspec{tests=Tests1});
@@ -768,7 +693,7 @@ add_tests([{cases,Nodes,Dir,Suite,Cs}|Ts],Spec) when is_list(Nodes) ->
add_tests([{cases,Node,Dir,Suite,Cs}|Ts],Spec) ->
Tests = Spec#testspec.tests,
Tests1 = insert_cases(ref2node(Node,Spec#testspec.nodes),
- ref2dir(Dir,Spec#testspec.alias),
+ ref2dir(Dir,Spec),
Suite,Cs,Tests, Spec#testspec.merge_tests),
add_tests(Ts,Spec#testspec{tests=Tests1});
@@ -783,7 +708,7 @@ add_tests([{skip_suites,Nodes,Dir,Ss,Cmt}|Ts],Spec) when is_list(Nodes) ->
add_tests([{skip_suites,Node,Dir,Ss,Cmt}|Ts],Spec) ->
Tests = Spec#testspec.tests,
Tests1 = skip_suites(ref2node(Node,Spec#testspec.nodes),
- ref2dir(Dir,Spec#testspec.alias),
+ ref2dir(Dir,Spec),
Ss,Cmt,Tests,
Spec#testspec.merge_tests),
add_tests(Ts,Spec#testspec{tests=Tests1});
@@ -792,7 +717,8 @@ add_tests([{skip_suites,Node,Dir,Ss,Cmt}|Ts],Spec) ->
add_tests([{skip_groups,all_nodes,Dir,Suite,Gs,Cmt}|Ts],Spec) ->
add_tests([{skip_groups,list_nodes(Spec),Dir,Suite,Gs,Cmt}|Ts],Spec);
add_tests([{skip_groups,all_nodes,Dir,Suite,Gs,{cases,TCs},Cmt}|Ts],Spec) ->
- add_tests([{skip_groups,list_nodes(Spec),Dir,Suite,Gs,{cases,TCs},Cmt}|Ts],Spec);
+ add_tests([{skip_groups,list_nodes(Spec),Dir,Suite,Gs,{cases,TCs},Cmt}|Ts],
+ Spec);
add_tests([{skip_groups,Dir,Suite,Gs,Cmt}|Ts],Spec) ->
add_tests([{skip_groups,all_nodes,Dir,Suite,Gs,Cmt}|Ts],Spec);
add_tests([{skip_groups,Dir,Suite,Gs,{cases,TCs},Cmt}|Ts],Spec) ->
@@ -800,20 +726,22 @@ add_tests([{skip_groups,Dir,Suite,Gs,{cases,TCs},Cmt}|Ts],Spec) ->
add_tests([{skip_groups,Nodes,Dir,Suite,Gs,Cmt}|Ts],Spec) when is_list(Nodes) ->
Ts1 = separate(Nodes,skip_groups,[Dir,Suite,Gs,Cmt],Ts,Spec#testspec.nodes),
add_tests(Ts1,Spec);
-add_tests([{skip_groups,Nodes,Dir,Suite,Gs,{cases,TCs},Cmt}|Ts],Spec) when is_list(Nodes) ->
- Ts1 = separate(Nodes,skip_groups,[Dir,Suite,Gs,{cases,TCs},Cmt],Ts,Spec#testspec.nodes),
+add_tests([{skip_groups,Nodes,Dir,Suite,Gs,{cases,TCs},Cmt}|Ts],
+ Spec) when is_list(Nodes) ->
+ Ts1 = separate(Nodes,skip_groups,[Dir,Suite,Gs,{cases,TCs},Cmt],Ts,
+ Spec#testspec.nodes),
add_tests(Ts1,Spec);
add_tests([{skip_groups,Node,Dir,Suite,Gs,Cmt}|Ts],Spec) ->
Tests = Spec#testspec.tests,
Tests1 = skip_groups(ref2node(Node,Spec#testspec.nodes),
- ref2dir(Dir,Spec#testspec.alias),
+ ref2dir(Dir,Spec),
Suite,Gs,all,Cmt,Tests,
Spec#testspec.merge_tests),
add_tests(Ts,Spec#testspec{tests=Tests1});
add_tests([{skip_groups,Node,Dir,Suite,Gs,{cases,TCs},Cmt}|Ts],Spec) ->
Tests = Spec#testspec.tests,
Tests1 = skip_groups(ref2node(Node,Spec#testspec.nodes),
- ref2dir(Dir,Spec#testspec.alias),
+ ref2dir(Dir,Spec),
Suite,Gs,TCs,Cmt,Tests,
Spec#testspec.merge_tests),
add_tests(Ts,Spec#testspec{tests=Tests1});
@@ -829,45 +757,101 @@ add_tests([{skip_cases,Nodes,Dir,Suite,Cs,Cmt}|Ts],Spec) when is_list(Nodes) ->
add_tests([{skip_cases,Node,Dir,Suite,Cs,Cmt}|Ts],Spec) ->
Tests = Spec#testspec.tests,
Tests1 = skip_cases(ref2node(Node,Spec#testspec.nodes),
- ref2dir(Dir,Spec#testspec.alias),
+ ref2dir(Dir,Spec),
Suite,Cs,Cmt,Tests,Spec#testspec.merge_tests),
add_tests(Ts,Spec#testspec{tests=Tests1});
+%% --- various configuration terms ---
+add_tests([{config,Nodes,CfgDir,Files}|Ts],Spec) when is_list(Nodes);
+ Nodes == all_nodes ->
+ add_tests([{config,Nodes,{CfgDir,Files}}|Ts],Spec);
+add_tests([{config,Node,CfgDir,FileOrFiles}|Ts],Spec) ->
+ add_tests([{config,Node,{CfgDir,FileOrFiles}}|Ts],Spec);
+add_tests([{config,CfgDir=[Ch|_],Files}|Ts],Spec) when is_integer(Ch) ->
+ add_tests([{config,all_nodes,{CfgDir,Files}}|Ts],Spec);
+
+add_tests([{event_handler,Nodes,Hs,Args}|Ts],Spec) when is_list(Nodes);
+ Nodes == all_nodes ->
+ add_tests([{event_handler,Nodes,{Hs,Args}}|Ts],Spec);
+add_tests([{event_handler,Node,HOrHs,Args}|Ts],Spec) ->
+ add_tests([{event_handler,Node,{HOrHs,Args}}|Ts],Spec);
+
+add_tests([{enable_builtin_hooks,Bool}|Ts],Spec) ->
+ add_tests(Ts, Spec#testspec{enable_builtin_hooks = Bool});
+
+add_tests([{noinput,Bool}|Ts],Spec) ->
+ add_tests(Ts, Spec#testspec{noinput = Bool});
+
%% --- handled/errors ---
+add_tests([{define,_,_}|Ts],Spec) -> % handled
+ add_tests(Ts,Spec);
+
add_tests([{alias,_,_}|Ts],Spec) -> % handled
add_tests(Ts,Spec);
add_tests([{node,_,_}|Ts],Spec) -> % handled
add_tests(Ts,Spec);
-add_tests([{merge_tests, _} | Ts], Spec) -> % handled
+add_tests([{merge_tests, _} | Ts], Spec) -> % handled
add_tests(Ts,Spec);
-%% check if it's a CT term that has bad format or if the user seems to
-%% have added something of his/her own, which we'll let pass if relaxed
-%% mode is enabled.
-add_tests([Other|Ts],Spec) when is_tuple(Other) ->
- [Name|_] = tuple_to_list(Other),
- case lists:keymember(Name,1,valid_terms()) of
- true -> % halt
- throw({error,{bad_term_in_spec,Other}});
- false -> % ignore
- case get(relaxed) of
- true ->
- %% warn if name resembles a CT term
- case resembles_ct_term(Name,size(Other)) of
- true ->
- io:format("~nSuspicious term, please check:~n"
- "~p~n", [Other]);
- false ->
- ok
- end,
- add_tests(Ts,Spec);
- false ->
- throw({error,{undefined_term_in_spec,Other}})
- end
+%% --------------------------------------------------
+%% / \
+%% | General add_tests/2 clauses below will work for |
+%% | most test spec configuration terms |
+%% \ /
+%% --------------------------------------------------
+
+%% create one test entry per known node and reinsert
+add_tests([Term={Tag,all_nodes,Data}|Ts],Spec) ->
+ case check_term(Term) of
+ valid ->
+ Tests = [{Tag,Node,Data} || Node <- list_nodes(Spec),
+ should_be_added(Tag,Node,Data,Spec)],
+ add_tests(Tests++Ts,Spec);
+ invalid -> % ignore term
+ add_tests(Ts,Spec)
end;
-
+%% create one test entry per node in Nodes and reinsert
+add_tests([{Tag,[],Data}|Ts],Spec) ->
+ add_tests([{Tag,all_nodes,Data}|Ts],Spec);
+add_tests([{Tag,String=[Ch|_],Data}|Ts],Spec) when is_integer(Ch) ->
+ add_tests([{Tag,all_nodes,{String,Data}}|Ts],Spec);
+add_tests([{Tag,NodesOrOther,Data}|Ts],Spec) when is_list(NodesOrOther) ->
+ case lists:all(fun(Test) -> is_node(Test,Spec#testspec.nodes)
+ end, NodesOrOther) of
+ true ->
+ Ts1 = separate(NodesOrOther,Tag,[Data],Ts,Spec#testspec.nodes),
+ add_tests(Ts1,Spec);
+ false ->
+ add_tests([{Tag,all_nodes,{NodesOrOther,Data}}|Ts],Spec)
+ end;
+%% update data for testspec term of type Tag
+add_tests([Term={Tag,NodeOrOther,Data}|Ts],Spec) ->
+ case is_node(NodeOrOther,Spec#testspec.nodes) of
+ true ->
+ case check_term(Term) of
+ valid ->
+ Node = ref2node(NodeOrOther,Spec#testspec.nodes),
+ NodeIxData =
+ update_recorded(Tag,Node,Spec) ++
+ handle_data(Tag,Node,Data,Spec),
+ add_tests(Ts,mod_field(Spec,Tag,NodeIxData));
+ invalid -> % ignore term
+ add_tests(Ts,Spec)
+ end;
+ false ->
+ add_tests([{Tag,all_nodes,{NodeOrOther,Data}}|Ts],Spec)
+ end;
+%% this test should be added for all known nodes
+add_tests([Term={Tag,Data}|Ts],Spec) ->
+ case check_term(Term) of
+ valid ->
+ add_tests([{Tag,all_nodes,Data}|Ts],Spec);
+ invalid ->
+ add_tests(Ts,Spec)
+ end;
+%% some other data than a tuple
add_tests([Other|Ts],Spec) ->
case get(relaxed) of
true ->
@@ -879,6 +863,112 @@ add_tests([Other|Ts],Spec) ->
add_tests([],Spec) -> % done
Spec.
+%% check if it's a CT term that has bad format or if the user seems to
+%% have added something of his/her own, which we'll let pass if relaxed
+%% mode is enabled.
+check_term(Term) ->
+ Size = size(Term),
+ [Name|_] = tuple_to_list(Term),
+ Valid = valid_terms(),
+ case lists:member({Name,Size},Valid) of
+ true ->
+ valid;
+ false ->
+ case lists:keymember(Name,1,Valid) of
+ true -> % halt
+ throw({error,{bad_term_in_spec,Term}});
+ false -> % ignore
+ case get(relaxed) of
+ true ->
+ %% warn if name resembles a CT term
+ case resembles_ct_term(Name,size(Term)) of
+ true ->
+ io:format("~nSuspicious term, "
+ "please check:~n"
+ "~p~n", [Term]),
+ invalid;
+ false ->
+ invalid
+ end;
+ false ->
+ throw({error,{undefined_term_in_spec,Term}})
+ end
+ end
+ end.
+
+%% specific data handling before saving in testspec record, e.g.
+%% converting relative paths to absolute for directories and files
+%% (introduce a clause *only* if the data value needs processing)
+handle_data(logdir,Node,Dir,Spec) ->
+ [{Node,ref2dir(Dir,Spec)}];
+handle_data(cover,Node,File,Spec) ->
+ [{Node,get_absfile(File,Spec)}];
+handle_data(include,Node,Dirs=[D|_],Spec) when is_list(D) ->
+ [{Node,ref2dir(Dir,Spec)} || Dir <- Dirs];
+handle_data(include,Node,Dir=[Ch|_],Spec) when is_integer(Ch) ->
+ handle_data(include,Node,[Dir],Spec);
+handle_data(config,Node,File=[Ch|_],Spec) when is_integer(Ch) ->
+ handle_data(config,Node,[File],Spec);
+handle_data(config,Node,{CfgDir,File=[Ch|_]},Spec) when is_integer(Ch) ->
+ handle_data(config,Node,{CfgDir,[File]},Spec);
+handle_data(config,Node,Files=[F|_],Spec) when is_list(F) ->
+ [{Node,get_absfile(File,Spec)} || File <- Files];
+handle_data(config,Node,{CfgDir,Files=[F|_]},Spec) when is_list(F) ->
+ [{Node,filename:join(ref2dir(CfgDir,Spec),File)} || File <- Files];
+handle_data(userconfig,Node,CBs,Spec) when is_list(CBs) ->
+ [{Node,{Callback,get_absfile(Callback,Config,Spec)}} ||
+ {Callback,Config} <- CBs];
+handle_data(userconfig,Node,CB,Spec) when is_tuple(CB) ->
+ handle_data(userconfig,Node,[CB],Spec);
+handle_data(event_handler,Node,H,Spec) when is_atom(H) ->
+ handle_data(event_handler,Node,{[H],[]},Spec);
+handle_data(event_handler,Node,{H,Args},Spec) when is_atom(H) ->
+ handle_data(event_handler,Node,{[H],Args},Spec);
+handle_data(event_handler,Node,Hs,_Spec) when is_list(Hs) ->
+ [{Node,EvH,[]} || EvH <- Hs];
+handle_data(event_handler,Node,{Hs,Args},_Spec) when is_list(Hs) ->
+ [{Node,EvH,Args} || EvH <- Hs];
+handle_data(ct_hooks,Node,Hooks,_Spec) when is_list(Hooks) ->
+ [{Node,Hook} || Hook <- Hooks ];
+handle_data(ct_hooks,Node,Hook,_Spec) ->
+ [{Node,Hook}];
+handle_data(stylesheet,Node,CSSFile,Spec) ->
+ [{Node,get_absfile(CSSFile,Spec)}];
+handle_data(verbosity,Node,VLvls,_Spec) when is_integer(VLvls) ->
+ [{Node,[{'$unspecified',VLvls}]}];
+handle_data(verbosity,Node,VLvls,_Spec) when is_list(VLvls) ->
+ VLvls1 = lists:map(fun(VLvl = {_Cat,_Lvl}) -> VLvl;
+ (Lvl) -> {'$unspecified',Lvl} end, VLvls),
+ [{Node,VLvls1}];
+handle_data(_Tag,Node,Data,_Spec) ->
+ [{Node,Data}].
+
+%% check if duplicates should be saved or not
+should_be_added(Tag,Node,_Data,Spec) ->
+ if
+ %% list terms *without* possible duplicates here
+ Tag == logdir; Tag == logopts;
+ Tag == basic_html; Tag == label;
+ Tag == auto_compile; Tag == stylesheet;
+ Tag == verbosity ->
+ lists:keymember(ref2node(Node,Spec#testspec.nodes),1,
+ read_field(Spec,Tag)) == false;
+ %% for terms *with* possible duplicates
+ true ->
+ true
+ end.
+
+%% check if previous elements for Node should be deleted
+update_recorded(Tag,Node,Spec) ->
+ if Tag == config; Tag == userconfig; Tag == event_handler;
+ Tag == ct_hooks; Tag == include ->
+ read_field(Spec,Tag);
+ true ->
+ %% delete previous value for Tag
+ lists:keydelete(Node,1,read_field(Spec,Tag))
+ end.
+
+%% create one test term per node
separate(Nodes,Tag,Data,Tests,Refs) ->
Separated = separate(Nodes,Tag,Data,Refs),
Separated ++ Tests.
@@ -886,7 +976,25 @@ separate([N|Ns],Tag,Data,Refs) ->
[list_to_tuple([Tag,ref2node(N,Refs)|Data])|separate(Ns,Tag,Data,Refs)];
separate([],_,_,_) ->
[].
-
+
+%% read the value for FieldName in record Rec#testspec
+read_field(Rec, FieldName) ->
+ catch lists:foldl(fun(F, Pos) when F == FieldName ->
+ throw(element(Pos, Rec));
+ (_,Pos) ->
+ Pos+1
+ end,2,?testspec_fields).
+
+%% modify the value for FieldName in record Rec#testspec
+mod_field(Rec, FieldName, NewVal) ->
+ [_testspec|RecList] = tuple_to_list(Rec),
+ RecList1 =
+ (catch lists:foldl(fun(F, {Prev,[_OldVal|Rest]}) when F == FieldName ->
+ throw(lists:reverse(Prev) ++ [NewVal|Rest]);
+ (_,{Prev,[Field|Rest]}) ->
+ {[Field|Prev],Rest}
+ end,{[],RecList},?testspec_fields)),
+ list_to_tuple([testspec|RecList1]).
%% Representation:
%% {{Node,Dir},[{Suite1,[GrOrCase11,GrOrCase12,...]},
@@ -1094,33 +1202,40 @@ ref2node(all_nodes,_Refs) ->
ref2node(master,_Refs) ->
master;
ref2node(RefOrNode,Refs) ->
- case string:chr(atom_to_list(RefOrNode),$@) of
- 0 -> % a ref
+ case lists:member($@,atom_to_list(RefOrNode)) of
+ false -> % a ref
case lists:keysearch(RefOrNode,1,Refs) of
{value,{RefOrNode,Node}} ->
Node;
false ->
throw({error,{noderef_missing,RefOrNode}})
end;
- _ -> % a node
+ true -> % a node
RefOrNode
end.
-ref2dir(Ref,Refs) when is_atom(Ref) ->
+ref2dir(Ref,Spec) ->
+ ref2dir(Ref,Spec#testspec.alias,Spec).
+
+ref2dir(Ref,Refs,Spec) when is_atom(Ref) ->
case lists:keysearch(Ref,1,Refs) of
{value,{Ref,Dir}} ->
- Dir;
+ get_absdir(Dir,Spec);
false ->
throw({error,{alias_missing,Ref}})
end;
-ref2dir(Dir,_) when is_list(Dir) ->
- Dir.
-
-is_noderef(What,Nodes) when is_atom(What) ->
- is_noderef([What],Nodes);
-is_noderef([master|_],_Nodes) ->
+ref2dir(Dir,_,Spec) when is_list(Dir) ->
+ get_absdir(Dir,Spec);
+ref2dir(What,_,_) ->
+ throw({error,{invalid_directory_name,What}}).
+
+is_node(What,Nodes) when is_atom(What) ->
+ is_node([What],Nodes);
+is_node([master|_],_Nodes) ->
true;
-is_noderef([What|_],Nodes) ->
+is_node(What={N,H},Nodes) when is_atom(N), is_atom(H) ->
+ is_node([What],Nodes);
+is_node([What|_],Nodes) ->
case lists:keymember(What,1,Nodes) or
lists:keymember(What,2,Nodes) of
true ->
@@ -1128,24 +1243,30 @@ is_noderef([What|_],Nodes) ->
false ->
false
end;
-is_noderef([],_) ->
+is_node([],_) ->
false.
valid_terms() ->
[
+ {define,3},
{node,3},
{cover,2},
{cover,3},
{config,2},
{config,3},
+ {config,4},
{userconfig,2},
{userconfig,3},
{alias,3},
- {merge_tests,1},
+ {merge_tests,2},
{logdir,2},
{logdir,3},
{logopts,2},
{logopts,3},
+ {basic_html,2},
+ {basic_html,3},
+ {verbosity,2},
+ {verbosity,3},
{label,2},
{label,3},
{event_handler,2},
@@ -1153,13 +1274,18 @@ valid_terms() ->
{event_handler,4},
{ct_hooks,2},
{ct_hooks,3},
- {enable_builtin_hooks,1},
+ {enable_builtin_hooks,2},
+ {noinput,2},
{multiply_timetraps,2},
{multiply_timetraps,3},
{scale_timetraps,2},
{scale_timetraps,3},
{include,2},
{include,3},
+ {auto_compile,2},
+ {auto_compile,3},
+ {stylesheet,2},
+ {stylesheet,3},
{suites,3},
{suites,4},
{groups,4},
@@ -1174,7 +1300,8 @@ valid_terms() ->
{skip_groups,7},
{skip_cases,5},
{skip_cases,6},
- {create_priv_dir,2}
+ {create_priv_dir,2},
+ {create_priv_dir,3}
].
%% this function "guesses" if the user has misspelled a term name
diff --git a/lib/common_test/src/ct_util.erl b/lib/common_test/src/ct_util.erl
index 66ecb142ca..efc85543ac 100644
--- a/lib/common_test/src/ct_util.erl
+++ b/lib/common_test/src/ct_util.erl
@@ -25,7 +25,8 @@
%%%
-module(ct_util).
--export([start/0,start/1,start/2,stop/1,update_last_run_index/0]).
+-export([start/0,start/1,start/2,start/3,
+ stop/1,update_last_run_index/0]).
-export([register_connection/4,unregister_connection/1,
does_connection_exist/3,get_key_from_name/1]).
@@ -36,14 +37,15 @@
save_suite_data_async/3, save_suite_data_async/2,
read_suite_data/1,
delete_suite_data/0, delete_suite_data/1, match_delete_suite_data/1,
- delete_testdata/0, delete_testdata/1, set_testdata/1, get_testdata/1,
+ delete_testdata/0, delete_testdata/1,
+ set_testdata/1, get_testdata/1, get_testdata/2,
set_testdata_async/1, update_testdata/2]).
-export([override_silence_all_connections/0, override_silence_connections/1,
get_overridden_silenced_connections/0,
delete_overridden_silenced_connections/0,
- silence_all_connections/0, silence_connections/1, is_silenced/1,
- reset_silent_connections/0]).
+ silence_all_connections/0, silence_connections/1,
+ is_silenced/1, is_silenced/2, reset_silent_connections/0]).
-export([get_mode/0, create_table/3, read_opts/0]).
@@ -64,9 +66,13 @@
-export([get_profile_data/0, get_profile_data/1,
get_profile_data/2, open_url/3]).
+-include("ct.hrl").
-include("ct_event.hrl").
-include("ct_util.hrl").
+-define(default_verbosity, [{default,?MAX_VERBOSITY},
+ {'$unspecified',?MAX_VERBOSITY}]).
+
-record(suite_data, {key,name,value}).
%%%-----------------------------------------------------------------
@@ -85,18 +91,21 @@
%%%
%%% @see ct
start() ->
- start(normal,".").
+ start(normal, ".", ?default_verbosity).
start(LogDir) when is_list(LogDir) ->
- start(normal,LogDir);
+ start(normal, LogDir, ?default_verbosity);
start(Mode) ->
- start(Mode,".").
+ start(Mode, ".", ?default_verbosity).
+
+start(LogDir, Verbosity) when is_list(LogDir) ->
+ start(normal, LogDir, Verbosity).
-start(Mode,LogDir) ->
+start(Mode, LogDir, Verbosity) ->
case whereis(ct_util_server) of
undefined ->
S = self(),
- Pid = spawn_link(fun() -> do_start(S,Mode,LogDir) end),
+ Pid = spawn_link(fun() -> do_start(S, Mode, LogDir, Verbosity) end),
receive
{Pid,started} -> Pid;
{Pid,Error} -> exit(Error);
@@ -113,7 +122,7 @@ start(Mode,LogDir) ->
end
end.
-do_start(Parent,Mode,LogDir) ->
+do_start(Parent, Mode, LogDir, Verbosity) ->
process_flag(trap_exit,true),
register(ct_util_server,self()),
create_table(?conn_table,#conn.handle),
@@ -173,7 +182,7 @@ do_start(Parent,Mode,LogDir) ->
false ->
ok
end,
- {StartTime,TestLogDir} = ct_logs:init(Mode),
+ {StartTime,TestLogDir} = ct_logs:init(Mode, Verbosity),
ct_event:notify(#event{name=test_start,
node=node(),
@@ -193,7 +202,7 @@ do_start(Parent,Mode,LogDir) ->
self() ! {{stop,{self(),{user_error,CTHReason}}},
{Parent,make_ref()}}
end,
- loop(Mode,[],StartDir).
+ loop(Mode, [{{verbosity,Cat},Lvl} || {Cat,Lvl} <- Verbosity], StartDir).
create_table(TableName,KeyPos) ->
create_table(TableName,set,KeyPos).
@@ -254,6 +263,9 @@ set_testdata_async(TestData) ->
get_testdata(Key) ->
call({get_testdata, Key}).
+get_testdata(Key, Timeout) ->
+ call({get_testdata, Key}, Timeout).
+
set_cwd(Dir) ->
call({set_cwd,Dir}).
@@ -388,7 +400,6 @@ loop(Mode,TestData,StartDir) ->
end
end.
-
close_connections([#conn{handle=Handle,callback=CB}|Conns]) ->
CB:close(Handle),
close_connections(Conns);
@@ -520,7 +531,7 @@ close_connections() ->
%%%
%%% @doc
override_silence_all_connections() ->
- Protocols = [telnet,ftp,rpc,snmp],
+ Protocols = [telnet,ftp,rpc,snmp,ssh],
override_silence_connections(Protocols),
Protocols.
@@ -557,7 +568,10 @@ silence_connections(Conns) when is_list(Conns) ->
set_testdata({silent_connections,Conns1}).
is_silenced(Conn) ->
- case get_testdata(silent_connections) of
+ is_silenced(Conn, infinity).
+
+is_silenced(Conn, Timeout) ->
+ case get_testdata(silent_connections, Timeout) of
Conns when is_list(Conns) ->
case lists:keysearch(Conn,1,Conns) of
{value,{Conn,true}} ->
@@ -565,6 +579,8 @@ is_silenced(Conn) ->
_ ->
false
end;
+ Error = {error,_} ->
+ Error;
_ ->
false
end.
@@ -839,19 +855,28 @@ get_profile_data(Profile, Key, StartDir) ->
%%%-----------------------------------------------------------------
%%% Internal functions
call(Msg) ->
- case whereis(ct_util_server) of
- undefined ->
+ call(Msg, infinity).
+
+call(Msg, Timeout) ->
+ case {self(),whereis(ct_util_server)} of
+ {_,undefined} ->
{error,ct_util_server_not_running};
- Pid ->
+ {Pid,Pid} ->
+ %% the caller is ct_util_server, which must
+ %% be a mistake
+ {error,bad_invocation};
+ {Self,Pid} ->
MRef = erlang:monitor(process, Pid),
Ref = make_ref(),
- ct_util_server ! {Msg,{self(),Ref}},
+ ct_util_server ! {Msg,{Self,Ref}},
receive
{Ref, Result} ->
erlang:demonitor(MRef, [flush]),
Result;
{'DOWN',MRef,process,_,Reason} ->
{error,{ct_util_server_down,Reason}}
+ after
+ Timeout -> {error,timeout}
end
end.
diff --git a/lib/common_test/src/ct_util.hrl b/lib/common_test/src/ct_util.hrl
index 474d36574e..7015014441 100644
--- a/lib/common_test/src/ct_util.hrl
+++ b/lib/common_test/src/ct_util.hrl
@@ -34,13 +34,18 @@
profile=[],
logdir=["."],
logopts=[],
+ basic_html=[],
+ verbosity=[],
cover=[],
config=[],
userconfig=[],
event_handler=[],
ct_hooks=[],
enable_builtin_hooks=true,
+ noinput=false,
include=[],
+ auto_compile=[],
+ stylesheet=[],
multiply_timetraps=[],
scale_timetraps=[],
create_priv_dir=[],
@@ -65,5 +70,10 @@
-define(ct_profile_file, ".common_test").
+-define(css_default, "ct_default.css").
+-define(sortable_table_name, "SortableTable").
+-define(jquery_script, "jquery-latest.js").
+-define(tablesorter_script, "jquery.tablesorter.min.js").
+
%% Logging information for error handler
-record(conn_log, {client, name, address, action, module}).
diff --git a/lib/common_test/test/Makefile b/lib/common_test/test/Makefile
index 4d85b84b5b..7628ada61a 100644
--- a/lib/common_test/test/Makefile
+++ b/lib/common_test/test/Makefile
@@ -39,6 +39,7 @@ MODULES= \
ct_sequence_1_SUITE \
ct_repeat_1_SUITE \
ct_testspec_1_SUITE \
+ ct_testspec_2_SUITE \
ct_skip_SUITE \
ct_error_SUITE \
ct_test_server_if_1_SUITE \
@@ -46,7 +47,10 @@ MODULES= \
ct_master_SUITE \
ct_misc_1_SUITE \
ct_hooks_SUITE \
- ct_netconfc_SUITE
+ ct_netconfc_SUITE \
+ ct_basic_html_SUITE \
+ ct_auto_compile_SUITE \
+ ct_verbosity_SUITE
ERL_FILES= $(MODULES:%=%.erl)
diff --git a/lib/common_test/test/ct_auto_compile_SUITE.erl b/lib/common_test/test/ct_auto_compile_SUITE.erl
new file mode 100644
index 0000000000..cc546ed30d
--- /dev/null
+++ b/lib/common_test/test/ct_auto_compile_SUITE.erl
@@ -0,0 +1,187 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2009-2012. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+%%%-------------------------------------------------------------------
+%%% File: ct_auto_compile_SUITE
+%%%
+%%% Description:
+%%%
+%%%
+%%% The suites used for the test are located in the data directory.
+%%%-------------------------------------------------------------------
+-module(ct_auto_compile_SUITE).
+
+-compile(export_all).
+
+-include_lib("common_test/include/ct.hrl").
+-include_lib("common_test/include/ct_event.hrl").
+
+-define(eh, ct_test_support_eh).
+
+%%--------------------------------------------------------------------
+%% TEST SERVER CALLBACK FUNCTIONS
+%%--------------------------------------------------------------------
+
+%%--------------------------------------------------------------------
+%% Description: Since Common Test starts another Test Server
+%% instance, the tests need to be performed on a separate node (or
+%% there will be clashes with logging processes etc).
+%%--------------------------------------------------------------------
+init_per_suite(Config) ->
+ Config1 = ct_test_support:init_per_suite(Config),
+ Config1.
+
+end_per_suite(Config) ->
+ ct_test_support:end_per_suite(Config).
+
+init_per_testcase(TestCase, Config) ->
+ ct_test_support:init_per_testcase(TestCase, Config).
+
+end_per_testcase(TestCase, Config) ->
+ ct_test_support:end_per_testcase(TestCase, Config).
+
+suite() -> [{ct_hooks,[ts_install_cth]}].
+
+all() ->
+ [ac_flag, ac_spec].
+
+groups() ->
+ [].
+
+init_per_group(_GroupName, Config) ->
+ Config.
+
+end_per_group(_GroupName, Config) ->
+ Config.
+
+
+
+%%--------------------------------------------------------------------
+%% TEST CASES
+%%--------------------------------------------------------------------
+
+%%%-----------------------------------------------------------------
+%%%
+ac_flag(Config) when is_list(Config) ->
+ DataDir = ?config(data_dir, Config),
+ PrivDir = ?config(priv_dir, Config),
+ file:copy(filename:join(DataDir, "bad_SUITE.erl"),
+ filename:join(PrivDir, "bad_SUITE.erl")),
+ Suite = filename:join(DataDir, "dummy_SUITE"),
+ compile:file(Suite, [{outdir,PrivDir}]),
+ {Opts,ERPid} = setup([{dir,PrivDir},
+ {auto_compile,false},
+ {label,"ac_flag"}],
+ Config),
+
+ ok = ct_test_support:run(Opts, Config),
+ Events = ct_test_support:get_events(ERPid, Config),
+
+ ct_test_support:log_events(ac_flag,
+ reformat(Events, ?eh),
+ PrivDir,
+ Opts),
+
+ TestEvents = events_to_check(ac_flag),
+ ok = ct_test_support:verify_events(TestEvents, Events, Config).
+
+%%%-----------------------------------------------------------------
+%%%
+ac_spec(Config) when is_list(Config) ->
+ DataDir = ?config(data_dir, Config),
+ PrivDir = ?config(priv_dir, Config),
+ file:copy(filename:join(DataDir, "bad_SUITE.erl"),
+ filename:join(PrivDir, "bad_SUITE.erl")),
+ TestSpec = [{label,ac_spec},
+ {auto_compile,false},
+ {suites,PrivDir,all}],
+ FileName = filename:join(?config(priv_dir, Config),"ac_spec.spec"),
+ {ok,Dev} = file:open(FileName, [write]),
+ [io:format(Dev, "~p.~n", [Term]) || Term <- TestSpec],
+ file:close(Dev),
+
+ {Opts,ERPid} = setup([{spec,FileName}], Config),
+ ok = ct_test_support:run(Opts, Config),
+ Events = ct_test_support:get_events(ERPid, Config),
+
+ ct_test_support:log_events(ac_spec,
+ reformat(Events, ?eh),
+ PrivDir,
+ Opts),
+
+ TestEvents = events_to_check(ac_spec),
+ ok = ct_test_support:verify_events(TestEvents, Events, Config).
+
+
+%%%-----------------------------------------------------------------
+%%% HELP FUNCTIONS
+%%%-----------------------------------------------------------------
+
+setup(Test, Config) ->
+ Opts0 = ct_test_support:get_opts(Config),
+ Level = ?config(trace_level, Config),
+ EvHArgs = [{cbm,ct_test_support},{trace_level,Level}],
+ Opts = Opts0 ++ [{event_handler,{?eh,EvHArgs}}|Test],
+ ERPid = ct_test_support:start_event_receiver(Config),
+ {Opts,ERPid}.
+
+reformat(Events, EH) ->
+ ct_test_support:reformat(Events, EH).
+ %reformat(Events, _EH) ->
+ % Events.
+
+%%%-----------------------------------------------------------------
+%%% TEST EVENTS
+%%%-----------------------------------------------------------------
+events_to_check(Test) ->
+ %% 2 tests (ct:run_test + script_start) is default
+ events_to_check(Test, 2).
+
+events_to_check(_, 0) ->
+ [];
+events_to_check(Test, N) ->
+ test_events(Test) ++ events_to_check(Test, N-1).
+
+test_events(ac_flag) ->
+ [
+ {ct_test_support_eh,start_logging,{'DEF','RUNDIR'}},
+ {ct_test_support_eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}},
+ {ct_test_support_eh,start_info,{1,1,3}},
+ {ct_test_support_eh,tc_start,{dummy_SUITE,init_per_suite}},
+ {ct_test_support_eh,tc_done,{dummy_SUITE,init_per_suite,ok}},
+ {ct_test_support_eh,test_stats,{1,1,{1,0}}},
+ {ct_test_support_eh,tc_start,{dummy_SUITE,end_per_suite}},
+ {ct_test_support_eh,tc_done,{dummy_SUITE,end_per_suite,ok}},
+ {ct_test_support_eh,test_done,{'DEF','STOP_TIME'}},
+ {ct_test_support_eh,stop_logging,[]}
+ ];
+
+test_events(ac_spec) ->
+ [
+ {ct_test_support_eh,start_logging,{'DEF','RUNDIR'}},
+ {ct_test_support_eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}},
+ {ct_test_support_eh,start_info,{1,1,3}},
+ {ct_test_support_eh,tc_start,{dummy_SUITE,init_per_suite}},
+ {ct_test_support_eh,tc_done,{dummy_SUITE,init_per_suite,ok}},
+ {ct_test_support_eh,test_stats,{1,1,{1,0}}},
+ {ct_test_support_eh,tc_start,{dummy_SUITE,end_per_suite}},
+ {ct_test_support_eh,tc_done,{dummy_SUITE,end_per_suite,ok}},
+ {ct_test_support_eh,test_done,{'DEF','STOP_TIME'}},
+ {ct_test_support_eh,stop_logging,[]}
+ ].
diff --git a/lib/common_test/test/ct_auto_compile_SUITE_data/bad_SUITE.erl b/lib/common_test/test/ct_auto_compile_SUITE_data/bad_SUITE.erl
new file mode 100644
index 0000000000..6ebcb3570e
--- /dev/null
+++ b/lib/common_test/test/ct_auto_compile_SUITE_data/bad_SUITE.erl
@@ -0,0 +1,23 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2009-2010. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+
+-module(bad_SUITE).
+
+-compile(export_all).
+
+bad_bad_suite
diff --git a/lib/common_test/test/ct_auto_compile_SUITE_data/dummy_SUITE.erl b/lib/common_test/test/ct_auto_compile_SUITE_data/dummy_SUITE.erl
new file mode 100644
index 0000000000..0b1eafc31d
--- /dev/null
+++ b/lib/common_test/test/ct_auto_compile_SUITE_data/dummy_SUITE.erl
@@ -0,0 +1,130 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2009-2010. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+
+-module(dummy_SUITE).
+
+-compile(export_all).
+
+-include_lib("common_test/include/ct.hrl").
+
+%%--------------------------------------------------------------------
+%% @spec suite() -> Info
+%% Info = [tuple()]
+%% @end
+%%--------------------------------------------------------------------
+suite() ->
+ [{timetrap,{seconds,30}}].
+
+%%--------------------------------------------------------------------
+%% @spec init_per_suite(Config0) ->
+%% Config1 | {skip,Reason} | {skip_and_save,Reason,Config1}
+%% Config0 = Config1 = [tuple()]
+%% Reason = term()
+%% @end
+%%--------------------------------------------------------------------
+init_per_suite(Config) ->
+ Config.
+
+%%--------------------------------------------------------------------
+%% @spec end_per_suite(Config0) -> void() | {save_config,Config1}
+%% Config0 = Config1 = [tuple()]
+%% @end
+%%--------------------------------------------------------------------
+end_per_suite(_Config) ->
+ ok.
+
+%%--------------------------------------------------------------------
+%% @spec init_per_group(GroupName, Config0) ->
+%% Config1 | {skip,Reason} | {skip_and_save,Reason,Config1}
+%% GroupName = atom()
+%% Config0 = Config1 = [tuple()]
+%% Reason = term()
+%% @end
+%%--------------------------------------------------------------------
+init_per_group(_GroupName, Config) ->
+ Config.
+
+%%--------------------------------------------------------------------
+%% @spec end_per_group(GroupName, Config0) ->
+%% void() | {save_config,Config1}
+%% GroupName = atom()
+%% Config0 = Config1 = [tuple()]
+%% @end
+%%--------------------------------------------------------------------
+end_per_group(_GroupName, _Config) ->
+ ok.
+
+%%--------------------------------------------------------------------
+%% @spec init_per_testcase(TestCase, Config0) ->
+%% Config1 | {skip,Reason} | {skip_and_save,Reason,Config1}
+%% TestCase = atom()
+%% Config0 = Config1 = [tuple()]
+%% Reason = term()
+%% @end
+%%--------------------------------------------------------------------
+init_per_testcase(_TestCase, Config) ->
+ Config.
+
+%%--------------------------------------------------------------------
+%% @spec end_per_testcase(TestCase, Config0) ->
+%% void() | {save_config,Config1} | {fail,Reason}
+%% TestCase = atom()
+%% Config0 = Config1 = [tuple()]
+%% Reason = term()
+%% @end
+%%--------------------------------------------------------------------
+end_per_testcase(_TestCase, _Config) ->
+ ok.
+
+%%--------------------------------------------------------------------
+%% @spec groups() -> [Group]
+%% Group = {GroupName,Properties,GroupsAndTestCases}
+%% GroupName = atom()
+%% Properties = [parallel | sequence | Shuffle | {RepeatType,N}]
+%% GroupsAndTestCases = [Group | {group,GroupName} | TestCase]
+%% TestCase = atom()
+%% Shuffle = shuffle | {shuffle,{integer(),integer(),integer()}}
+%% RepeatType = repeat | repeat_until_all_ok | repeat_until_all_fail |
+%% repeat_until_any_ok | repeat_until_any_fail
+%% N = integer() | forever
+%% @end
+%%--------------------------------------------------------------------
+groups() ->
+ [].
+
+%%--------------------------------------------------------------------
+%% @spec all() -> GroupsAndTestCases | {skip,Reason}
+%% GroupsAndTestCases = [{group,GroupName} | TestCase]
+%% GroupName = atom()
+%% TestCase = atom()
+%% Reason = term()
+%% @end
+%%--------------------------------------------------------------------
+all() ->
+ [ok,fail,skip].
+
+
+ok(_Config) ->
+ ok.
+
+fail(Config) ->
+ tuple_to_list(Config),
+ ok.
+
+skip(_Config) ->
+ {skip,"should be skipped"}.
diff --git a/lib/common_test/test/ct_basic_html_SUITE.erl b/lib/common_test/test/ct_basic_html_SUITE.erl
new file mode 100644
index 0000000000..a5f2e6197e
--- /dev/null
+++ b/lib/common_test/test/ct_basic_html_SUITE.erl
@@ -0,0 +1,180 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2009-2012. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+%%%-------------------------------------------------------------------
+%%% File: ct_basic_html_SUITE
+%%%
+%%% Description:
+%%%
+%%%
+%%% The suites used for the test are located in the data directory.
+%%%-------------------------------------------------------------------
+-module(ct_basic_html_SUITE).
+
+-compile(export_all).
+
+-include_lib("common_test/include/ct.hrl").
+-include_lib("common_test/include/ct_event.hrl").
+
+-define(eh, ct_test_support_eh).
+
+%%--------------------------------------------------------------------
+%% TEST SERVER CALLBACK FUNCTIONS
+%%--------------------------------------------------------------------
+
+%%--------------------------------------------------------------------
+%% Description: Since Common Test starts another Test Server
+%% instance, the tests need to be performed on a separate node (or
+%% there will be clashes with logging processes etc).
+%%--------------------------------------------------------------------
+init_per_suite(Config) ->
+ Config1 = ct_test_support:init_per_suite(Config),
+ Config1.
+
+end_per_suite(Config) ->
+ ct_test_support:end_per_suite(Config).
+
+init_per_testcase(TestCase, Config) ->
+ ct_test_support:init_per_testcase(TestCase, Config).
+
+end_per_testcase(TestCase, Config) ->
+ ct_test_support:end_per_testcase(TestCase, Config).
+
+suite() -> [{ct_hooks,[ts_install_cth]}].
+
+all() ->
+ [basic_flag, basic_spec].
+
+groups() ->
+ [].
+
+init_per_group(_GroupName, Config) ->
+ Config.
+
+end_per_group(_GroupName, Config) ->
+ Config.
+
+
+
+%%--------------------------------------------------------------------
+%% TEST CASES
+%%--------------------------------------------------------------------
+
+%%%-----------------------------------------------------------------
+%%%
+basic_flag(Config) when is_list(Config) ->
+ DataDir = ?config(data_dir, Config),
+ Suites = [filename:join(DataDir, "babbling_SUITE")],
+ {Opts,ERPid} = setup([{suite,Suites},
+ {basic_html,true},
+ {label,"basic_flag"}],
+ Config),
+
+ ok = ct_test_support:run(Opts, Config),
+ Events = ct_test_support:get_events(ERPid, Config),
+
+ ct_test_support:log_events(basic_flag,
+ reformat(Events, ?eh),
+ ?config(priv_dir, Config),
+ Opts),
+
+ TestEvents = events_to_check(basic_flag),
+ ok = ct_test_support:verify_events(TestEvents, Events, Config).
+
+%%%-----------------------------------------------------------------
+%%%
+basic_spec(Config) when is_list(Config) ->
+ DataDir = ?config(data_dir, Config),
+ TestSpec = [{label,basic_spec},
+ {basic_html,true},
+ {suites,DataDir,babbling_SUITE}],
+ FileName = filename:join(?config(priv_dir, Config),"basic_spec.spec"),
+ {ok,Dev} = file:open(FileName, [write]),
+ [io:format(Dev, "~p.~n", [Term]) || Term <- TestSpec],
+ file:close(Dev),
+
+ {Opts,ERPid} = setup([{spec,FileName}], Config),
+ ok = ct_test_support:run(Opts, Config),
+ Events = ct_test_support:get_events(ERPid, Config),
+
+ ct_test_support:log_events(basic_spec,
+ reformat(Events, ?eh),
+ ?config(priv_dir, Config),
+ Opts),
+
+ TestEvents = events_to_check(basic_spec),
+ ok = ct_test_support:verify_events(TestEvents, Events, Config).
+
+
+%%%-----------------------------------------------------------------
+%%% HELP FUNCTIONS
+%%%-----------------------------------------------------------------
+
+setup(Test, Config) ->
+ Opts0 = ct_test_support:get_opts(Config),
+ Level = ?config(trace_level, Config),
+ EvHArgs = [{cbm,ct_test_support},{trace_level,Level}],
+ Opts = Opts0 ++ [{event_handler,{?eh,EvHArgs}}|Test],
+ ERPid = ct_test_support:start_event_receiver(Config),
+ {Opts,ERPid}.
+
+reformat(Events, EH) ->
+ ct_test_support:reformat(Events, EH).
+ %reformat(Events, _EH) ->
+ % Events.
+
+%%%-----------------------------------------------------------------
+%%% TEST EVENTS
+%%%-----------------------------------------------------------------
+events_to_check(Test) ->
+ %% 2 tests (ct:run_test + script_start) is default
+ events_to_check(Test, 2).
+
+events_to_check(_, 0) ->
+ [];
+events_to_check(Test, N) ->
+ test_events(Test) ++ events_to_check(Test, N-1).
+
+test_events(basic_flag) ->
+ [
+ {ct_test_support_eh,start_logging,{'DEF','RUNDIR'}},
+ {ct_test_support_eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}},
+ {ct_test_support_eh,start_info,{1,1,3}},
+ {ct_test_support_eh,tc_start,{babbling_SUITE,init_per_suite}},
+ {ct_test_support_eh,tc_done,{babbling_SUITE,init_per_suite,ok}},
+ {ct_test_support_eh,test_stats,{1,1,{1,0}}},
+ {ct_test_support_eh,tc_start,{babbling_SUITE,end_per_suite}},
+ {ct_test_support_eh,tc_done,{babbling_SUITE,end_per_suite,ok}},
+ {ct_test_support_eh,test_done,{'DEF','STOP_TIME'}},
+ {ct_test_support_eh,stop_logging,[]}
+ ];
+
+test_events(basic_spec) ->
+ [
+ {ct_test_support_eh,start_logging,{'DEF','RUNDIR'}},
+ {ct_test_support_eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}},
+ {ct_test_support_eh,start_info,{1,1,3}},
+ {ct_test_support_eh,tc_start,{babbling_SUITE,init_per_suite}},
+ {ct_test_support_eh,tc_done,{babbling_SUITE,init_per_suite,ok}},
+ {ct_test_support_eh,test_stats,{1,1,{1,0}}},
+ {ct_test_support_eh,tc_start,{babbling_SUITE,end_per_suite}},
+ {ct_test_support_eh,tc_done,{babbling_SUITE,end_per_suite,ok}},
+ {ct_test_support_eh,test_done,{'DEF','STOP_TIME'}},
+ {ct_test_support_eh,stop_logging,[]}
+ ].
diff --git a/lib/common_test/test/ct_basic_html_SUITE_data/babbling_SUITE.erl b/lib/common_test/test/ct_basic_html_SUITE_data/babbling_SUITE.erl
new file mode 100644
index 0000000000..d67383c606
--- /dev/null
+++ b/lib/common_test/test/ct_basic_html_SUITE_data/babbling_SUITE.erl
@@ -0,0 +1,130 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2009-2010. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+
+-module(babbling_SUITE).
+
+-compile(export_all).
+
+-include_lib("common_test/include/ct.hrl").
+
+%%--------------------------------------------------------------------
+%% @spec suite() -> Info
+%% Info = [tuple()]
+%% @end
+%%--------------------------------------------------------------------
+suite() ->
+ [{timetrap,{seconds,30}}].
+
+%%--------------------------------------------------------------------
+%% @spec init_per_suite(Config0) ->
+%% Config1 | {skip,Reason} | {skip_and_save,Reason,Config1}
+%% Config0 = Config1 = [tuple()]
+%% Reason = term()
+%% @end
+%%--------------------------------------------------------------------
+init_per_suite(Config) ->
+ Config.
+
+%%--------------------------------------------------------------------
+%% @spec end_per_suite(Config0) -> void() | {save_config,Config1}
+%% Config0 = Config1 = [tuple()]
+%% @end
+%%--------------------------------------------------------------------
+end_per_suite(_Config) ->
+ ok.
+
+%%--------------------------------------------------------------------
+%% @spec init_per_group(GroupName, Config0) ->
+%% Config1 | {skip,Reason} | {skip_and_save,Reason,Config1}
+%% GroupName = atom()
+%% Config0 = Config1 = [tuple()]
+%% Reason = term()
+%% @end
+%%--------------------------------------------------------------------
+init_per_group(_GroupName, Config) ->
+ Config.
+
+%%--------------------------------------------------------------------
+%% @spec end_per_group(GroupName, Config0) ->
+%% void() | {save_config,Config1}
+%% GroupName = atom()
+%% Config0 = Config1 = [tuple()]
+%% @end
+%%--------------------------------------------------------------------
+end_per_group(_GroupName, _Config) ->
+ ok.
+
+%%--------------------------------------------------------------------
+%% @spec init_per_testcase(TestCase, Config0) ->
+%% Config1 | {skip,Reason} | {skip_and_save,Reason,Config1}
+%% TestCase = atom()
+%% Config0 = Config1 = [tuple()]
+%% Reason = term()
+%% @end
+%%--------------------------------------------------------------------
+init_per_testcase(_TestCase, Config) ->
+ Config.
+
+%%--------------------------------------------------------------------
+%% @spec end_per_testcase(TestCase, Config0) ->
+%% void() | {save_config,Config1} | {fail,Reason}
+%% TestCase = atom()
+%% Config0 = Config1 = [tuple()]
+%% Reason = term()
+%% @end
+%%--------------------------------------------------------------------
+end_per_testcase(_TestCase, _Config) ->
+ ok.
+
+%%--------------------------------------------------------------------
+%% @spec groups() -> [Group]
+%% Group = {GroupName,Properties,GroupsAndTestCases}
+%% GroupName = atom()
+%% Properties = [parallel | sequence | Shuffle | {RepeatType,N}]
+%% GroupsAndTestCases = [Group | {group,GroupName} | TestCase]
+%% TestCase = atom()
+%% Shuffle = shuffle | {shuffle,{integer(),integer(),integer()}}
+%% RepeatType = repeat | repeat_until_all_ok | repeat_until_all_fail |
+%% repeat_until_any_ok | repeat_until_any_fail
+%% N = integer() | forever
+%% @end
+%%--------------------------------------------------------------------
+groups() ->
+ [].
+
+%%--------------------------------------------------------------------
+%% @spec all() -> GroupsAndTestCases | {skip,Reason}
+%% GroupsAndTestCases = [{group,GroupName} | TestCase]
+%% GroupName = atom()
+%% TestCase = atom()
+%% Reason = term()
+%% @end
+%%--------------------------------------------------------------------
+all() ->
+ [ok,fail,skip].
+
+
+ok(_Config) ->
+ ok.
+
+fail(Config) ->
+ tuple_to_list(Config),
+ ok.
+
+skip(_Config) ->
+ {skip,"should be skipped"}.
diff --git a/lib/common_test/test/ct_error_SUITE.erl b/lib/common_test/test/ct_error_SUITE.erl
index c9ee47e01b..338e76264e 100644
--- a/lib/common_test/test/ct_error_SUITE.erl
+++ b/lib/common_test/test/ct_error_SUITE.erl
@@ -878,11 +878,11 @@ test_events(timetrap_fun) ->
{failed,{timetrap_timeout,{'$approx',1000}}}}},
{?eh,test_stats,{0,5,{0,0}}},
{?eh,tc_start,{timetrap_5_SUITE,tc1}},
- {?eh,tc_done,{undefined,undefined,{user_timetrap_error,
+ {?eh,tc_done,{timetrap_5_SUITE,tc1,{user_timetrap_error,
{kaboom,'_'}}}},
{?eh,test_stats,{0,6,{0,0}}},
{?eh,tc_start,{timetrap_5_SUITE,tc2}},
- {?eh,tc_done,{undefined,undefined,{user_timetrap_error,
+ {?eh,tc_done,{timetrap_5_SUITE,tc2,{user_timetrap_error,
{kaboom,'_'}}}},
{?eh,test_stats,{0,7,{0,0}}},
{?eh,tc_start,{timetrap_5_SUITE,tc3}},
@@ -937,7 +937,7 @@ test_events(timetrap_fun) ->
{?eh,tc_done,{timetrap_5_SUITE,end_per_suite,ok}},
{?eh,tc_start,{timetrap_6_SUITE,init_per_suite}},
- {?eh,tc_done,{undefined,undefined,{user_timetrap_error,
+ {?eh,tc_done,{timetrap_6_SUITE,init_per_suite,{user_timetrap_error,
{kaboom,'_'}}}},
{?eh,tc_auto_skip,{timetrap_6_SUITE,tc0,
{failed,{timetrap_6_SUITE,init_per_suite,
diff --git a/lib/common_test/test/ct_misc_1_SUITE.erl b/lib/common_test/test/ct_misc_1_SUITE.erl
index cb17af9ab5..d2318de445 100644
--- a/lib/common_test/test/ct_misc_1_SUITE.erl
+++ b/lib/common_test/test/ct_misc_1_SUITE.erl
@@ -106,7 +106,7 @@ beam_me_up(Config) when is_list(Config) ->
{Opts,ERPid} = setup([{suite,Suites},{auto_compile,false}], Config),
- ok = ct_test_support:run(ct, run_test, [Opts], Config),
+ {_Ok,_Fail,_Skip} = ct_test_support:run(ct, run_test, [Opts], Config),
Events = ct_test_support:get_events(ERPid, Config),
ct_test_support:log_events(beam_me_up,
diff --git a/lib/common_test/test/ct_priv_dir_SUITE_data/priv_dir_SUITE.erl b/lib/common_test/test/ct_priv_dir_SUITE_data/priv_dir_SUITE.erl
index 423cb2999b..7704a29768 100644
--- a/lib/common_test/test/ct_priv_dir_SUITE_data/priv_dir_SUITE.erl
+++ b/lib/common_test/test/ct_priv_dir_SUITE_data/priv_dir_SUITE.erl
@@ -1,11 +1,21 @@
-%%%-------------------------------------------------------------------
-%%% @author Peter Andersson <[email protected]>
-%%% @copyright (C) 2012, Peter Andersson
-%%% @doc
-%%%
-%%% @end
-%%% Created : 23 Jan 2012 by Peter Andersson <[email protected]>
-%%%-------------------------------------------------------------------
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2009-2012. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
-module(priv_dir_SUITE).
-compile(export_all).
diff --git a/lib/common_test/test/ct_test_support.erl b/lib/common_test/test/ct_test_support.erl
index 02246b5763..80cca4a1cc 100644
--- a/lib/common_test/test/ct_test_support.erl
+++ b/lib/common_test/test/ct_test_support.erl
@@ -35,6 +35,8 @@
verify_events/3, reformat/2, log_events/4,
join_abs_dirs/2]).
+-export([ct_test_halt/1]).
+
-include_lib("kernel/include/file.hrl").
%%%-----------------------------------------------------------------
@@ -229,8 +231,9 @@ run(Opts, Config) when is_list(Opts) ->
%% use ct interface
test_server:format(Level, "~n[RUN #1] Calling ct:run_test(~p) on ~p~n",
[Opts, CTNode]),
- Result1 = rpc:call(CTNode, ct, run_test, [Opts]),
-
+ CtRunTestResult = rpc:call(CTNode, ct, run_test, [Opts]),
+ test_server:format(Level, "~n[RUN #1] Got return value ~p~n",
+ [CtRunTestResult]),
case rpc:call(CTNode, erlang, whereis, [ct_util_server]) of
undefined ->
ok;
@@ -242,17 +245,36 @@ run(Opts, Config) when is_list(Opts) ->
undefined = rpc:call(CTNode, erlang, whereis, [ct_util_server])
end,
%% use run_test interface (simulated)
- test_server:format(Level, "Saving start opts on ~p: ~p~n", [CTNode,Opts]),
- rpc:call(CTNode, application, set_env, [common_test, run_test_start_opts, Opts]),
- test_server:format(Level, "[RUN #2] Calling ct_run:script_start() on ~p~n", [CTNode]),
- Result2 = rpc:call(CTNode, ct_run, script_start, []),
- case {Result1,Result2} of
- {ok,ok} ->
+ Opts1 = [{halt_with,{?MODULE,ct_test_halt}} | Opts],
+ test_server:format(Level, "Saving start opts on ~p: ~p~n",
+ [CTNode, Opts1]),
+ rpc:call(CTNode, application, set_env,
+ [common_test, run_test_start_opts, Opts1]),
+ test_server:format(Level, "[RUN #2] Calling ct_run:script_start() on ~p~n",
+ [CTNode]),
+ ExitStatus = rpc:call(CTNode, ct_run, script_start, []),
+ test_server:format(Level, "[RUN #2] Got exit status value ~p~n",
+ [ExitStatus]),
+ case {CtRunTestResult,ExitStatus} of
+ {{_Ok,Failed,{_UserSkipped,_AutoSkipped}},1} when Failed > 0 ->
+ ok;
+ {{_Ok,0,{_UserSkipped,AutoSkipped}},ExitStatus} when AutoSkipped > 0 ->
+ case proplists:get_value(exit_status, Opts1) of
+ ignore_config when ExitStatus == 1 ->
+ {error,{wrong_exit_status,ExitStatus}};
+ _ ->
+ ok
+ end;
+ {{error,_}=Error,ExitStatus} ->
+ if ExitStatus /= 2 ->
+ {error,{wrong_exit_status,ExitStatus}};
+ ExitStatus == 2 ->
+ Error
+ end;
+ {{_Ok,0,{_UserSkipped,_AutoSkipped}},0} ->
ok;
- {E,_} when E =/= ok ->
- E;
- {_,E} when E =/= ok ->
- E
+ Unexpected ->
+ {error,{unexpected_return_value,Unexpected}}
end.
run(M, F, A, Config) ->
@@ -272,6 +294,10 @@ run({M,F,A}, InitCalls, Config) ->
[M, F, A, CTNode]),
rpc:call(CTNode, M, F, A).
+%% this is the last function that ct_run:script_start() calls, so the
+%% return value here is what rpc:call/4 above returns
+ct_test_halt(ExitStatus) ->
+ ExitStatus.
%%%-----------------------------------------------------------------
%%% wait_for_ct_stop/1
diff --git a/lib/common_test/test/ct_testspec_1_SUITE.erl b/lib/common_test/test/ct_testspec_1_SUITE.erl
index b6dcf63fdf..693e8c6567 100644
--- a/lib/common_test/test/ct_testspec_1_SUITE.erl
+++ b/lib/common_test/test/ct_testspec_1_SUITE.erl
@@ -621,7 +621,9 @@ setup_and_execute(TCName, TestSpec, Config) ->
ok = ct_test_support:run(Opts, Config),
TestSpec1 = [{logdir,proplists:get_value(logdir,Opts)},
{label,proplists:get_value(label,TestTerms)} | TestSpec],
- ok = ct_test_support:run(ct, run_testspec, [TestSpec1], Config),
+ {_Ok,_Failed,{_USkipped,_ASkipped}} =
+ ct_test_support:run(ct, run_testspec, [TestSpec1], Config),
+
Events = ct_test_support:get_events(ERPid, Config),
ct_test_support:log_events(TCName,
diff --git a/lib/common_test/test/ct_testspec_2_SUITE.erl b/lib/common_test/test/ct_testspec_2_SUITE.erl
new file mode 100644
index 0000000000..93f3520f95
--- /dev/null
+++ b/lib/common_test/test/ct_testspec_2_SUITE.erl
@@ -0,0 +1,751 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2009-2011. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+%%%-------------------------------------------------------------------
+%%% File: ct_testspec_2_SUITE
+%%%
+%%% Description:
+%%% Test test specifications
+%%%
+%%% The suites used for the test are located in the data directory.
+%%%-------------------------------------------------------------------
+-module(ct_testspec_2_SUITE).
+
+-compile(export_all).
+
+-include_lib("common_test/include/ct.hrl").
+-include_lib("common_test/src/ct_util.hrl").
+
+%%--------------------------------------------------------------------
+%% TEST SERVER CALLBACK FUNCTIONS
+%%--------------------------------------------------------------------
+
+init_per_suite(Config) ->
+ Config.
+
+end_per_suite(_Config) ->
+ ok.
+
+init_per_testcase(_TestCase, Config) ->
+ Config.
+
+end_per_testcase(_TestCase, _Config) ->
+ ok.
+
+%% suite() -> [{ct_hooks,[ts_install_cth]}].
+
+all() ->
+ [basic_compatible_no_nodes,
+ basic_compatible_nodes,
+ unknown_terms,
+ no_merging,
+ multiple_specs,
+ misc_config_terms,
+ define_names_1].
+
+
+%%--------------------------------------------------------------------
+%% VALID TEST SPEC TERMS (R15B02):
+%%
+%% {node,3}
+%% {cover,2}
+%% {cover,3}
+%% {config,2}
+%% {config,3}
+%% {config,4}
+%% {userconfig,2}
+%% {userconfig,3}
+%% {alias,3}
+%% {merge_tests,2}
+%% {logdir,2}
+%% {logdir,3}
+%% {logopts,2}
+%% {logopts,3}
+%% {basic_html,2}
+%% {basic_html,3}
+%% {label,2}
+%% {label,3}
+%% {event_handler,2}
+%% {event_handler,3}
+%% {event_handler,4}
+%% {ct_hooks,2}
+%% {ct_hooks,3}
+%% {enable_builtin_hooks,2}
+%% {noinput,2}
+%% {multiply_timetraps,2}
+%% {multiply_timetraps,3}
+%% {scale_timetraps,2}
+%% {scale_timetraps,3}
+%% {include,2}
+%% {include,3}
+%% {auto_compile,2}
+%% {auto_compile,3}
+%% {stylesheet,2}
+%% {stylesheet,3}
+%% {suites,3}
+%% {suites,4}
+%% {groups,4}
+%% {groups,5}
+%% {groups,6}
+%% {cases,4}
+%% {cases,5}
+%% {skip_suites,4}
+%% {skip_suites,5}
+%% {skip_groups,5}
+%% {skip_groups,6}
+%% {skip_groups,7}
+%% {skip_cases,5}
+%% {skip_cases,6}
+%% {create_priv_dir,2}
+%%
+%%--------------------------------------------------------------------
+
+%%--------------------------------------------------------------------
+%% TEST CASES
+%%--------------------------------------------------------------------
+
+%%%-----------------------------------------------------------------
+%%%
+basic_compatible_no_nodes(_Config) ->
+
+ AliasDir1 = "../tests/to1",
+ AliasDir2 = "../tests/to2",
+ CfgDir1 = "../cfgs/to1/x.cfg",
+ CfgDir2 = ["../cfgs/to2/x.cfg","../cfgs/to2/y.cfg"],
+ LogDir = "../logs",
+ IncludeDir1 = "../../include",
+ IncludeDir2 = ["../tests/to1/include","../tests/to2/include"],
+
+ Spec =
+ [
+ {label,"basic_compatible_no_nodes"},
+ {alias,to1,AliasDir1},
+ {alias,to2,AliasDir2},
+ {config,CfgDir1},
+ {config,CfgDir2},
+ {userconfig,{?MODULE,"cfg_str1"}},
+ {userconfig,{?MODULE,"cfg_str2"}},
+ {logdir,LogDir},
+ {logopts,[no_nl]},
+ {event_handler,evh1,[1]},
+ {event_handler,[evh2,evh3],[[2,3]]},
+ {ct_hooks,[{cth_mod1,[]}]},
+ {ct_hooks,[{cth_mod2,[]}]},
+ {multiply_timetraps,2},
+ {include,IncludeDir1},
+ {include,IncludeDir2},
+ {suites,to1,[x_SUITE]},
+ {groups,to1,y_SUITE,[g1,g2]},
+ {cases,to1,y_SUITE,[tc1,tc2]},
+ {skip_suites,to1,z_SUITE,"skipped"},
+ {suites,to2,[x_SUITE,y_SUITE]},
+ {skip_groups,to2,x_SUITE,[g1,g2],"skipped"},
+ {skip_cases,to2,y_SUITE,[tc1,tc2],"skipped"}
+ ],
+
+ {ok,SpecDir} = file:get_cwd(),
+
+ ListResult = ct_testspec:collect_tests_from_list(Spec, false),
+ ct:pal("TESTSPEC RECORD FROM LIST:~n~p~n", [rec2proplist(ListResult)]),
+ SpecFile = ct_test_support:write_testspec(Spec,SpecDir,
+ "basic_compatible_no_nodes.spec"),
+ FileResult = ct_testspec:collect_tests_from_file([SpecFile], false),
+ ct:pal("TESTSPEC RECORD FROM FILE:~n~p~n", [rec2proplist(FileResult)]),
+
+ Node = node(),
+ LogDirV = get_absdir(filename:join(SpecDir,"../logs")),
+ Alias1V = get_absdir(filename:join(SpecDir,AliasDir1)),
+ Alias2V = get_absdir(filename:join(SpecDir,AliasDir2)),
+ CFGs = [{Node,get_absdir(filename:join(SpecDir,CfgDir))} ||
+ CfgDir <- [CfgDir1 | CfgDir2]],
+ Incls = [{Node,get_absdir(filename:join(SpecDir,IncludeDir))} ||
+ IncludeDir <- [IncludeDir1 | IncludeDir2]],
+
+ Verify = #testspec{spec_dir = SpecDir,
+ nodes = [{undefined,Node}],
+ init = [],
+ label = [{Node,"basic_compatible_no_nodes"}],
+ logdir = [{Node,LogDirV},"."],
+ logopts = [{Node,[no_nl]}],
+ basic_html = [],
+ cover = [],
+ config = CFGs,
+ userconfig = [{Node,{?MODULE,"cfg_str1"}},
+ {Node,{?MODULE,"cfg_str2"}}],
+ event_handler = [{Node,evh1,[1]},
+ {Node,evh2,[[2,3]]},
+ {Node,evh3,[[2,3]]}],
+ ct_hooks = [{Node,{cth_mod1,[]}},
+ {Node,{cth_mod2,[]}}],
+ enable_builtin_hooks = true,
+ noinput = false,
+ include = Incls,
+ auto_compile = [],
+ stylesheet = [],
+ multiply_timetraps = [{Node,2}],
+ scale_timetraps = [],
+ create_priv_dir = [],
+ alias = [{to1,Alias1V},{to2,Alias2V}],
+ tests = [{{Node,Alias1V},
+ [{x_SUITE,[all]},
+ {y_SUITE,[{g1,all},{g2,all},tc1,tc2]},
+ {z_SUITE,[{all,{skip,"skipped"}}]}]},
+ {{Node,Alias2V},
+ [{x_SUITE,[all,
+ {{g1,all},{skip,"skipped"}},
+ {{g2,all},{skip,"skipped"}}]},
+ {y_SUITE,[all,
+ {tc1,{skip,"skipped"}},
+ {tc2,{skip,"skipped"}}]}]}],
+ merge_tests = true},
+
+ verify_result(Verify,ListResult,FileResult).
+
+%%%-----------------------------------------------------------------
+%%%
+basic_compatible_nodes(_Config) ->
+
+ Node1 = node1@host1,
+ Node2 = node2@host2,
+ TODir1 = "../tests/to1",
+ TODir2 = "../tests/to2",
+ CfgDir1 = "../cfgs/to1/x.cfg",
+ CfgDir2 = ["../cfgs/to2/x.cfg","../cfgs/to2/y.cfg"],
+ LogDir = "../logs",
+ MasterLogDir = "../master_logs",
+ IncludeDir1 = "../../include",
+ IncludeDir2 = ["../tests/to1/include","../tests/to2/include"],
+
+ Spec =
+ [
+ {node,n1,Node1},
+ {node,n2,Node2},
+ {init,[n1],[{node_start,[{callback_module,cbm}]}]},
+ {init,n2,[{node_start,[]}]},
+ {init,all_nodes,{eval,{mod,func,[]}}},
+ {label,"basic_compatible_nodes"},
+ {label,n1,basic_compatible_nodes_1},
+ {config,n1,CfgDir1},
+ {config,n2,CfgDir2},
+ {userconfig,{?MODULE,"cfg_str1"}},
+ {userconfig,{?MODULE,"cfg_str2"}},
+ {logdir,all_nodes,LogDir},
+ {logdir,master,MasterLogDir},
+ {logopts,node2@host2,[no_nl]},
+ {event_handler,master,evh1,[1]},
+ {event_handler,[n1,n2],[evh2,evh3],[[2,3]]},
+ {ct_hooks,all_nodes,[{cth_mod1,[]}]},
+ {ct_hooks,[{cth_mod2,[]}]},
+ {multiply_timetraps,node1@host1,2},
+ {include,n1,IncludeDir1},
+ {include,[n1,n2],IncludeDir2},
+ {suites,n1,TODir1,[x_SUITE]},
+ {groups,n1,TODir1,y_SUITE,[g1,g2]},
+ {cases,n1,TODir1,y_SUITE,[tc1,tc2]},
+ {skip_suites,n1,TODir1,z_SUITE,"skipped"},
+ {suites,n2,TODir2,[x_SUITE,y_SUITE]},
+ {skip_groups,n2,TODir2,x_SUITE,[g1,g2],"skipped"},
+ {skip_cases,n2,TODir2,y_SUITE,[tc1,tc2],"skipped"}
+ ],
+
+ {ok,SpecDir} = file:get_cwd(),
+
+ ListResult = ct_testspec:collect_tests_from_list(Spec, false),
+ ct:pal("TESTSPEC RECORD FROM LIST:~n~p~n", [rec2proplist(ListResult)]),
+ SpecFile = ct_test_support:write_testspec(Spec,SpecDir,
+ "basic_compatible_nodes.spec"),
+ FileResult = ct_testspec:collect_tests_from_file([SpecFile], false),
+ ct:pal("TESTSPEC RECORD FROM FILE:~n~p~n", [rec2proplist(FileResult)]),
+
+ Node = node(),
+ LogDirV = get_absdir(filename:join(SpecDir,"../logs")),
+ MasterLogDirV = get_absdir(filename:join(SpecDir,"../master_logs")),
+ TO1V = get_absdir(filename:join(SpecDir,TODir1)),
+ TO2V = get_absdir(filename:join(SpecDir,TODir2)),
+ CFGs = [{Node1,get_absdir(filename:join(SpecDir,CfgDir1))} |
+ [{Node2,get_absdir(filename:join(SpecDir,CfgDir))} || CfgDir <- CfgDir2]],
+ Incls = [{Node1,get_absdir(filename:join(SpecDir,IncludeDir1))} |
+ [{Node1,get_absdir(filename:join(SpecDir,IncludeDir))} ||
+ IncludeDir <- IncludeDir2] ++
+ [{Node2,get_absdir(filename:join(SpecDir,IncludeDir))} ||
+ IncludeDir <- IncludeDir2]],
+
+ Verify = #testspec{spec_dir = SpecDir,
+ nodes = [{undefined,Node},{n1,Node1},{n2,Node2}],
+ init = [{Node1,[{node_start,[{callback_module,cbm}]},
+ {eval,[{mod,func,[]}]}]},
+ {Node2,[{node_start,[{callback_module,ct_slave}]},
+ {eval,[{mod,func,[]}]}]},
+ {Node,[{node_start,[]},
+ {eval,[{mod,func,[]}]}]}],
+ label = [{Node,"basic_compatible_nodes"},
+ {Node2,"basic_compatible_nodes"},
+ {Node1,basic_compatible_nodes_1}],
+ logdir = [{Node,LogDirV},{Node1,LogDirV},{Node2,LogDirV},
+ {master,MasterLogDirV},"."],
+ logopts = [{Node2,[no_nl]}],
+ basic_html = [],
+ cover = [],
+ config = CFGs,
+ userconfig = [{Node,{?MODULE,"cfg_str1"}},
+ {Node1,{?MODULE,"cfg_str1"}},
+ {Node2,{?MODULE,"cfg_str1"}},
+ {Node,{?MODULE,"cfg_str2"}},
+ {Node1,{?MODULE,"cfg_str2"}},
+ {Node2,{?MODULE,"cfg_str2"}}],
+ event_handler = [{master,evh1,[1]},
+ {Node1,evh2,[[2,3]]},
+ {Node1,evh3,[[2,3]]},
+ {Node2,evh2,[[2,3]]},
+ {Node2,evh3,[[2,3]]}],
+ ct_hooks = [{Node,{cth_mod1,[]}},
+ {Node1,{cth_mod1,[]}},
+ {Node2,{cth_mod1,[]}},
+ {Node,{cth_mod2,[]}},
+ {Node1,{cth_mod2,[]}},
+ {Node2,{cth_mod2,[]}}],
+ enable_builtin_hooks = true,
+ noinput = false,
+ include = Incls,
+ auto_compile = [],
+ stylesheet = [],
+ multiply_timetraps = [{Node1,2}],
+ scale_timetraps = [],
+ create_priv_dir = [],
+ tests = [{{Node1,TO1V},
+ [{x_SUITE,[all]},
+ {y_SUITE,[{g1,all},{g2,all},tc1,tc2]},
+ {z_SUITE,[{all,{skip,"skipped"}}]}]},
+ {{Node2,TO2V},
+ [{x_SUITE,[all,
+ {{g1,all},{skip,"skipped"}},
+ {{g2,all},{skip,"skipped"}}]},
+ {y_SUITE,[all,
+ {tc1,{skip,"skipped"}},
+ {tc2,{skip,"skipped"}}]}]}],
+ merge_tests = true},
+
+ verify_result(Verify,ListResult,FileResult).
+
+%%%-----------------------------------------------------------------
+%%%
+unknown_terms(Config) ->
+ PrivDir = ?config(priv_dir, Config),
+
+ Spec1 = [{suites,PrivDir,all},
+ {userdata,"I've got news for you"}],
+ {error,{undefined_term_in_spec,{userdata,_}}} =
+ (catch ct_testspec:collect_tests_from_list(Spec1, false)),
+ true = is_record(ct_testspec:collect_tests_from_list(Spec1, true),
+ testspec),
+
+ Spec2 = [{logdir,{logdir,PrivDir}}],
+ {error,{invalid_directory_name,_}} =
+ (catch ct_testspec:collect_tests_from_list(Spec2, false)),
+
+ Spec3 = [{suite,PrivDir,all}],
+ {error,{undefined_term_in_spec,{suite,_,_}}} =
+ (catch ct_testspec:collect_tests_from_list(Spec3, false)),
+ true = is_record(ct_testspec:collect_tests_from_list(Spec3, true), testspec),
+
+ Spec4 = [{suites,PrivDir,all},
+ {skip_suites,PrivDir,x_SUITE}],
+ {error,{bad_term_in_spec,{skip_suites,_,_}}} =
+ (catch ct_testspec:collect_tests_from_list(Spec4, false)),
+ {error,{bad_term_in_spec,{skip_suites,_,_}}} =
+ (catch ct_testspec:collect_tests_from_list(Spec4, true)),
+
+ Spec5 = [{configs,all_nodes,PrivDir}],
+ {error,{undefined_term_in_spec,{configs,_,_}}} =
+ (catch ct_testspec:collect_tests_from_list(Spec5, false)),
+ true = is_record(ct_testspec:collect_tests_from_list(Spec5, true), testspec),
+
+ ok.
+
+%%%-----------------------------------------------------------------
+%%%
+no_merging(_Config) ->
+ Node1 = node1@host1,
+ Node2 = node2@host2,
+ TODir1 = "../tests/to1",
+ TODir2 = "../tests/to2",
+ Spec =
+ [
+ {merge_tests,false},
+ {node,n1,Node1},
+ {node,n2,Node2},
+ {suites,n1,TODir1,[x_SUITE]},
+ {groups,n1,TODir1,y_SUITE,[g1,g2]},
+ {cases,n1,TODir1,y_SUITE,[tc1,tc2]},
+ {skip_suites,n1,TODir1,z_SUITE,"skipped"},
+ {suites,n2,TODir2,[x_SUITE,y_SUITE]},
+ {skip_groups,n2,TODir2,x_SUITE,[g1,g2],"skipped"},
+ {skip_cases,n2,TODir2,y_SUITE,[tc1,tc2],"skipped"}
+ ],
+
+ {ok,SpecDir} = file:get_cwd(),
+
+ ListResult = ct_testspec:collect_tests_from_list(Spec, false),
+ ct:pal("TESTSPEC RECORD FROM LIST:~n~p~n", [rec2proplist(ListResult)]),
+ SpecFile = ct_test_support:write_testspec(Spec,SpecDir,
+ "no_merging.spec"),
+ FileResult = ct_testspec:collect_tests_from_file([SpecFile], false),
+ ct:pal("TESTSPEC RECORD FROM FILE:~n~p~n", [rec2proplist(FileResult)]),
+
+ Node = node(),
+ TO1V = get_absdir(filename:join(SpecDir,TODir1)),
+ TO2V = get_absdir(filename:join(SpecDir,TODir2)),
+
+ Verify = #testspec{merge_tests = false,
+ spec_dir = SpecDir,
+ nodes = [{undefined,Node},{n1,Node1},{n2,Node2}],
+ tests = [{{Node1,TO1V},
+ [{x_SUITE,[all]}]},
+ {{Node1,TO1V},
+ [{y_SUITE,[{g1,all},{g2,all}]}]},
+ {{Node1,TO1V},
+ [{y_SUITE,[tc1,tc2]}]},
+ {{Node1,TO1V},
+ [{z_SUITE,[{all,{skip,"skipped"}}]}]},
+ {{Node2,TO2V},
+ [{x_SUITE,[all]}]},
+ {{Node2,TO2V},
+ [{y_SUITE,[all]}]},
+ {{Node2,TO2V},
+ [{x_SUITE,[{{g1,all},{skip,"skipped"}},
+ {{g2,all},{skip,"skipped"}}]}]},
+ {{Node2,TO2V},
+ [{y_SUITE,[{tc1,{skip,"skipped"}},
+ {tc2,{skip,"skipped"}}]}]}]},
+
+ verify_result(Verify,ListResult,FileResult).
+
+%%%-----------------------------------------------------------------
+%%%
+multiple_specs(_Config) ->
+ Node1 = node1@host1,
+ Node2 = node2@host2,
+ TODir1 = "../tests/to1",
+ TODir2 = "../tests/to2",
+ CfgDir1 = "../cfgs/to1/x.cfg",
+ CfgDir2 = ["../cfgs/to2/x.cfg","../cfgs/to2/y.cfg"],
+ LogDir = "../logs",
+ Spec1 =
+ [
+ {node,n1,Node1},
+ {node,n2,Node2},
+ {alias,to1,TODir1},
+ {alias,to2,TODir2},
+ {label,"multiple_specs1"},
+ {config,n1,CfgDir1},
+ {config,n2,CfgDir2},
+ {logdir,all_nodes,LogDir}
+ ],
+ Spec2 =
+ [
+ {merge_tests,false},
+ {label,"multiple_specs2"},
+ {suites,n1,TODir1,[x_SUITE]},
+ {groups,n1,TODir1,y_SUITE,[g1,g2]},
+ {cases,n1,TODir1,y_SUITE,[tc1,tc2]},
+ {skip_suites,n1,TODir1,z_SUITE,"skipped"},
+ {suites,n2,TODir2,[x_SUITE,y_SUITE]},
+ {skip_groups,n2,TODir2,x_SUITE,[g1,g2],"skipped"},
+ {skip_cases,n2,TODir2,y_SUITE,[tc1,tc2],"skipped"}
+ ],
+
+ {ok,SpecDir} = file:get_cwd(),
+ SpecFile1 = ct_test_support:write_testspec(Spec1,SpecDir,
+ "multiple_specs.1.spec"),
+ SpecFile2 = ct_test_support:write_testspec(Spec2,SpecDir,
+ "multiple_specs.2.spec"),
+ FileResult = ct_testspec:collect_tests_from_file([SpecFile1,SpecFile2],
+ false),
+ ct:pal("TESTSPEC RECORD FROM FILE:~n~p~n", [rec2proplist(FileResult)]),
+
+ Node = node(),
+ TO1V = get_absdir(filename:join(SpecDir,TODir1)),
+ TO2V = get_absdir(filename:join(SpecDir,TODir2)),
+ CFGs = [{Node1,get_absdir(filename:join(SpecDir,CfgDir1))} |
+ [{Node2,get_absdir(filename:join(SpecDir,CfgDir))} || CfgDir <- CfgDir2]],
+ LogDirV = get_absdir(filename:join(SpecDir,"../logs")),
+
+ Verify = #testspec{merge_tests = false,
+ spec_dir = SpecDir,
+ nodes = [{undefined,Node},{n1,Node1},{n2,Node2}],
+ alias = [{to1,TO1V},{to2,TO2V}],
+ label = [{Node,"multiple_specs1"},
+ {Node1,"multiple_specs1"},
+ {Node2,"multiple_specs1"}],
+ logdir = [{Node,LogDirV},{Node1,LogDirV},{Node2,LogDirV},"."],
+ config = CFGs,
+ tests = [{{Node1,TO1V},
+ [{x_SUITE,[all]}]},
+ {{Node1,TO1V},
+ [{y_SUITE,[{g1,all},{g2,all}]}]},
+ {{Node1,TO1V},
+ [{y_SUITE,[tc1,tc2]}]},
+ {{Node1,TO1V},
+ [{z_SUITE,[{all,{skip,"skipped"}}]}]},
+ {{Node2,TO2V},
+ [{x_SUITE,[all]}]},
+ {{Node2,TO2V},
+ [{y_SUITE,[all]}]},
+ {{Node2,TO2V},
+ [{x_SUITE,[{{g1,all},{skip,"skipped"}},
+ {{g2,all},{skip,"skipped"}}]}]},
+ {{Node2,TO2V},
+ [{y_SUITE,[{tc1,{skip,"skipped"}},
+ {tc2,{skip,"skipped"}}]}]}]},
+
+ verify_result(Verify,FileResult,FileResult).
+
+%%%-----------------------------------------------------------------
+%%%
+misc_config_terms(_Config) ->
+ CfgDir = "../cfgs/to1",
+
+ Spec =
+ [{node,x,n1@h1},{node,y,n2@h2},
+
+ {config,CfgDir,"a.cfg"},
+ {config,n1@h1,CfgDir,"b.cfg"},
+ {config,all_nodes,CfgDir,"c.cfg"},
+ {config,all_nodes,filename:join(CfgDir,"d.cfg")},
+
+ {basic_html,true},
+ {basic_html,n1@h1,false},
+ {basic_html,n2@h2,true},
+
+ {enable_builtin_hooks,false},
+
+ {noinput,true},
+
+ {auto_compile,false},
+ {auto_compile,n1@h1,true},
+ {auto_compile,n2@h2,false},
+
+ {stylesheet,"../css"},
+ {stylesheet,n1@h1,"./n1/css"},
+ {stylesheet,n2@h2,"./n2/css"},
+
+ {create_priv_dir,[auto_per_tc]},
+ {create_priv_dir,n1@h1,[manual_per_tc]},
+ {create_priv_dir,n2@h2,[auto_per_run]}
+ ],
+
+ {ok,SpecDir} = file:get_cwd(),
+
+ ListResult = ct_testspec:collect_tests_from_list(Spec, false),
+ ct:pal("TESTSPEC RECORD FROM LIST:~n~p~n", [rec2proplist(ListResult)]),
+ SpecFile = ct_test_support:write_testspec(Spec,SpecDir,
+ "misc_config_terms.spec"),
+ FileResult = ct_testspec:collect_tests_from_file([SpecFile], false),
+ ct:pal("TESTSPEC RECORD FROM FILE:~n~p~n", [rec2proplist(FileResult)]),
+
+ Node = node(),
+ CfgA = get_absdir(filename:join(filename:join(SpecDir,CfgDir), "a.cfg")),
+ CfgB = get_absdir(filename:join(filename:join(SpecDir,CfgDir), "b.cfg")),
+ CfgC = get_absdir(filename:join(filename:join(SpecDir,CfgDir), "c.cfg")),
+ CfgD = get_absdir(filename:join(filename:join(SpecDir,CfgDir), "d.cfg")),
+ CSS = get_absdir(filename:join(SpecDir,"../css")),
+ CSS1 = get_absdir(filename:join(SpecDir,"./n1/css")),
+ CSS2 = get_absdir(filename:join(SpecDir,"./n2/css")),
+
+ Verify = #testspec{spec_dir = SpecDir,
+ nodes = [{undefined,Node},{x,n1@h1},{y,n2@h2}],
+ basic_html = [{Node,true},{n1@h1,false},{n2@h2,true}],
+ config = [{Node,CfgA},
+ {n1@h1,CfgA},
+ {n2@h2,CfgA},
+ {n1@h1,CfgB},
+ {Node,CfgC},
+ {n1@h1,CfgC},
+ {n2@h2,CfgC},
+ {Node,CfgD},
+ {n1@h1,CfgD},
+ {n2@h2,CfgD}],
+ enable_builtin_hooks = false,
+ noinput = true,
+ auto_compile = [{Node,false},
+ {n1@h1,true},
+ {n2@h2,false}],
+ stylesheet = [{Node,CSS},
+ {n1@h1,CSS1},
+ {n2@h2,CSS2}],
+ create_priv_dir = [{Node,[auto_per_tc]},
+ {n1@h1,[manual_per_tc]},
+ {n2@h2,[auto_per_run]}]
+ },
+
+ verify_result(Verify,ListResult,FileResult).
+
+%%%-----------------------------------------------------------------
+%%%
+define_names_1(_Config) ->
+ Spec =
+ [
+ {define,'HOST','eniac'},
+ {define,'NODE1',testnode1},
+ {define,'NODE2',testnode2},
+ {define,'NODES',['NODE1@HOST',
+ 'NODE2@HOST']},
+ {define,'TOPDIR',".."},
+ {define,'TO1',"to1"},
+ {define,'TO2',"to2"},
+ {define,'LOGDIR',"'TOPDIR'/logdir"},
+ {define,'LOGDIR1',"'TOPDIR'/logdir1"},
+ {define,'LOGDIR2',"'TOPDIR'/logdir2"},
+ {define,'CFGDIR',"'TOPDIR'/cfgs"},
+ {define,'CFGFILES',["cfgX","cfgY"]},
+ {define,'TESTDIR',"'TOPDIR'/test"},
+ {define,'TO1DIR',"'TESTDIR'/'TO1'"},
+ {define,'TO2DIR',"'TESTDIR'/'TO2'"},
+ {define,'EXSUITE',ex_SUITE},
+ {define,'EXGRS',[g1,g2]},
+
+ {logdir,'LOGDIR'},
+ {logdir,'NODE1@HOST','LOGDIR1'},
+ {logdir,'NODE2@HOST','LOGDIR2'},
+
+ {config,["a.cfg","b.cfg"]},
+ {config,'NODES',"./'CFGDIR'/c.cfg"},
+ {config,'CFGDIR',["d.cfg","e.cfg"]},
+ {config,'NODE2@HOST','CFGDIR','CFGFILES'},
+
+ {suites,'NODE1@HOST','TO1DIR',all},
+ {suites,'NODES','TO2DIR',all},
+
+ {groups,'TO1DIR','EXSUITE','EXGRS'}
+ ],
+
+ {ok,SpecDir} = file:get_cwd(),
+
+ ListResult = ct_testspec:collect_tests_from_list(Spec, false),
+ ct:pal("TESTSPEC RECORD FROM LIST:~n~p~n", [rec2proplist(ListResult)]),
+ SpecFile = ct_test_support:write_testspec(Spec,SpecDir,
+ "define_names_1.spec"),
+ FileResult = ct_testspec:collect_tests_from_file([SpecFile], false),
+ ct:pal("TESTSPEC RECORD FROM FILE:~n~p~n", [rec2proplist(FileResult)]),
+
+ N = node(),
+ N1 = testnode1@eniac,
+ N2 = testnode2@eniac,
+ Join = fun(Dir) -> shorten_path(filename:join(SpecDir,Dir),SpecDir) end,
+
+ Verify = #testspec{spec_dir = SpecDir,
+ nodes = [{undefined,N2},
+ {undefined,N1},
+ {undefined,N}],
+ config = [{N2,Join("a.cfg")},{N2,Join("b.cfg")},
+ {N1,Join("a.cfg")},{N1,Join("b.cfg")},
+ {N,Join("a.cfg")},{N,Join("b.cfg")},
+ {N1,Join("../cfgs/c.cfg")},{N2,Join("../cfgs/c.cfg")},
+ {N2,Join("../cfgs/d.cfg")},{N2,Join("../cfgs/e.cfg")},
+ {N1,Join("../cfgs/d.cfg")},{N1,Join("../cfgs/e.cfg")},
+ {N,Join("../cfgs/d.cfg")},{N,Join("../cfgs/e.cfg")},
+ {N2,Join("../cfgs/cfgX")},{N2,Join("../cfgs/cfgY")}],
+ logdir = [{N,Join("../logdir")},
+ {N1,Join("../logdir1")},
+ {N2,Join("../logdir2")},
+ "."],
+ tests = [{{N1,Join("../test/to1")},[{all,[all]}]},
+ {{N1,Join("../test/to2")},[{all,[all]}]},
+ {{N2,Join("../test/to2")},[{all,[all]}]},
+ {{N2,Join("../test/to1")},
+ [{ex_SUITE,[{g1,all},{g2,all}]}]},
+ {{N,Join("../test/to1")},
+ [{ex_SUITE,[{g1,all},{g2,all}]}]}]
+ },
+ verify_result(Verify,ListResult,FileResult).
+
+
+%%%-----------------------------------------------------------------
+%%% HELP FUNCTIONS
+%%%-----------------------------------------------------------------
+
+verify_result(Verify,ListResult,FileResult) ->
+ {_,TSLTuples} = rec2proplist(ListResult),
+ {_,TSFTuples} = rec2proplist(FileResult),
+ {_,VTuples} = rec2proplist(Verify),
+ VResult =
+ (catch lists:foldl(fun({Tag,Val},{[{Tag,Val}|TSL],[{Tag,Val}|TSF]}) ->
+ {TSL,TSF};
+ ({Tag,Val},{[{_Tag,TSLVal}|_TSL],[{Tag,Val}|_TSF]}) ->
+ exit({ts_list_mismatch,Tag,Val,TSLVal});
+ ({Tag,Val},{[{Tag,Val}|_TSL],[{_Tag,TSFVal}|_TSF]}) ->
+ exit({ts_file_mismatch,Tag,Val,TSFVal});
+ ({Tag,Val},{_,[{_Tag,TSFVal}|_TSF]}) ->
+ exit({ts_mismatch,Tag,Val,TSFVal})
+ end, {TSLTuples,TSFTuples}, VTuples)),
+ case VResult of
+ {'EXIT',Reason} ->
+ ct:fail(Reason);
+ _ ->
+ ok
+ end,
+ ok.
+
+
+check_parameter(S="cfg_str1") ->
+ {ok,{config,S}};
+check_parameter(S="cfg_str2") ->
+ {ok,{config,S}}.
+read_config(S) ->
+ {ok,[{cfg,S}]}.
+
+rec2proplist(E={error,_What}) ->
+ exit({invalid_testspec_record,E});
+rec2proplist(Rec) ->
+ [RecName|RecList] = tuple_to_list(Rec),
+ FieldNames =
+ if RecName == testspec ->
+ record_info(fields, testspec);
+ true ->
+ undefined
+ end,
+ {RecName,combine_names_and_vals(FieldNames,RecList)}.
+
+combine_names_and_vals([FN|FNs], [V|Vs]) ->
+ [{FN,V} | combine_names_and_vals(FNs, Vs)];
+combine_names_and_vals([], []) ->
+ [];
+combine_names_and_vals(_, _) ->
+ [].
+
+get_absdir(Dir) ->
+ shorten_path(filename:absname(Dir),Dir).
+
+shorten_path(Path,SpecDir) ->
+ case shorten_split_path(filename:split(Path),[]) of
+ [] ->
+ [Root|_] = filename:split(SpecDir),
+ Root;
+ Short ->
+ filename:join(Short)
+ end.
+
+shorten_split_path([".."|Path],SoFar) ->
+ shorten_split_path(Path,tl(SoFar));
+shorten_split_path(["."|Path],SoFar) ->
+ shorten_split_path(Path,SoFar);
+shorten_split_path([Dir|Path],SoFar) ->
+ shorten_split_path(Path,[Dir|SoFar]);
+shorten_split_path([],SoFar) ->
+ lists:reverse(SoFar).
diff --git a/lib/common_test/test/ct_verbosity_SUITE.erl b/lib/common_test/test/ct_verbosity_SUITE.erl
new file mode 100644
index 0000000000..349319de94
--- /dev/null
+++ b/lib/common_test/test/ct_verbosity_SUITE.erl
@@ -0,0 +1,244 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2009-2012. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+%%%-------------------------------------------------------------------
+%%% File: ct_verbosity_SUITE
+%%%
+%%% Description:
+%%% Test that verbosity levels vs the importance parameter works as
+%%% expected.
+%%%
+%%%-------------------------------------------------------------------
+-module(ct_verbosity_SUITE).
+
+-compile(export_all).
+
+-include_lib("common_test/include/ct.hrl").
+-include_lib("common_test/include/ct_event.hrl").
+
+-define(eh, ct_test_support_eh).
+
+%%--------------------------------------------------------------------
+%% TEST SERVER CALLBACK FUNCTIONS
+%%--------------------------------------------------------------------
+
+%%--------------------------------------------------------------------
+%% Description: Since Common Test starts another Test Server
+%% instance, the tests need to be performed on a separate node (or
+%% there will be clashes with logging processes etc).
+%%--------------------------------------------------------------------
+init_per_suite(Config) ->
+ Config1 = ct_test_support:init_per_suite(Config),
+ Config1.
+
+end_per_suite(Config) ->
+ ct_test_support:end_per_suite(Config).
+
+init_per_testcase(TestCase, Config) ->
+ ct_test_support:init_per_testcase(TestCase, Config).
+
+end_per_testcase(TestCase, Config) ->
+ ct_test_support:end_per_testcase(TestCase, Config).
+
+suite() -> [{ct_hooks,[ts_install_cth]}].
+
+all() ->
+ [
+ no_levels,
+ general_level_low,
+ general_level_std,
+ general_level_hi,
+ change_default,
+ combine_categories,
+ testspec_only,
+ merge_with_testspec
+ ].
+
+%%--------------------------------------------------------------------
+%% TEST CASES
+%%--------------------------------------------------------------------
+
+%%%-----------------------------------------------------------------
+%%%
+no_levels(Config) ->
+ TC = no_levels,
+ DataDir = ?config(data_dir, Config),
+ Suite = filename:join(DataDir, "io_test_SUITE"),
+ {Opts,ERPid} = setup([{suite,Suite},{label,TC}], Config),
+ ok = execute(TC, Opts, ERPid, Config).
+
+%%%-----------------------------------------------------------------
+%%%
+general_level_low(Config) ->
+ TC = general_level_low,
+ DataDir = ?config(data_dir, Config),
+ Suite = filename:join(DataDir, "io_test_SUITE"),
+ {Opts,ERPid} = setup([{suite,Suite},{label,TC},
+ {verbosity,0}], Config),
+ ok = execute(TC, Opts, ERPid, Config).
+
+%%%-----------------------------------------------------------------
+%%%
+general_level_std(Config) ->
+ TC = general_level_std,
+ DataDir = ?config(data_dir, Config),
+ Suite = filename:join(DataDir, "io_test_SUITE"),
+ {Opts,ERPid} = setup([{suite,Suite},{label,TC},
+ {verbosity,50}], Config),
+ ok = execute(TC, Opts, ERPid, Config).
+
+%%%-----------------------------------------------------------------
+%%%
+general_level_hi(Config) ->
+ TC = general_level_high,
+ DataDir = ?config(data_dir, Config),
+ Suite = filename:join(DataDir, "io_test_SUITE"),
+ {Opts,ERPid} = setup([{suite,Suite},{label,TC},
+ {verbosity,100}], Config),
+ ok = execute(TC, Opts, ERPid, Config).
+
+%%%-----------------------------------------------------------------
+%%%
+change_default(Config) ->
+ TC = change_default,
+ DataDir = ?config(data_dir, Config),
+ Suite = filename:join(DataDir, "io_test_SUITE"),
+ {Opts,ERPid} = setup([{suite,Suite},{label,TC},
+ {verbosity,[{default,49}]}], Config),
+ ok = execute(TC, Opts, ERPid, Config).
+
+%%%-----------------------------------------------------------------
+%%%
+combine_categories(Config) ->
+ TC = combine_categories,
+ DataDir = ?config(data_dir, Config),
+ Suite = filename:join(DataDir, "io_test_SUITE"),
+ {Opts,ERPid} = setup([{suite,Suite},{label,TC},
+ {verbosity,[{error,?HI_VERBOSITY},
+ {default,?LOW_VERBOSITY}]}], Config),
+ ok = execute(TC, Opts, ERPid, Config).
+
+%%%-----------------------------------------------------------------
+%%%
+testspec_only(Config) ->
+ TC = testspec_only,
+ DataDir = ?config(data_dir, Config),
+ PrivDir = ?config(priv_dir, Config),
+
+ TestSpec = [{verbosity,[{default,1},{error,75},100]},
+ {suites,DataDir,[io_test_SUITE]},
+ {label,TC}],
+
+ TestSpecName = ct_test_support:write_testspec(TestSpec, PrivDir,
+ "verbosity_1_spec"),
+ {Opts,ERPid} = setup([{spec,TestSpecName}], Config),
+
+ ok = execute(TC, Opts, ERPid, Config).
+
+%%%-----------------------------------------------------------------
+%%%
+merge_with_testspec(Config) ->
+ TC = merge_with_testspec,
+ DataDir = ?config(data_dir, Config),
+ PrivDir = ?config(priv_dir, Config),
+
+ TestSpec = [{verbosity,[{default,100},{error,100}]},
+ {suites,DataDir,[io_test_SUITE]},
+ {label,TC}],
+
+ TestSpecName = ct_test_support:write_testspec(TestSpec, PrivDir,
+ "verbosity_2_spec"),
+
+ %% below should override verbosity categories in testspec
+ {Opts,ERPid} = setup([{spec,TestSpecName},
+ {verbosity,[{default,0},0]}],
+ Config),
+
+ ok = execute(TC, Opts, ERPid, Config).
+
+%%%-----------------------------------------------------------------
+%%% HELP FUNCTIONS
+%%%-----------------------------------------------------------------
+
+setup(Test, Config) ->
+ Opts0 = ct_test_support:get_opts(Config),
+ Level = ?config(trace_level, Config),
+ EvHArgs = [{cbm,ct_test_support},{trace_level,Level}],
+ Opts = Opts0 ++ [{event_handler,{?eh,EvHArgs}}|Test],
+ ERPid = ct_test_support:start_event_receiver(Config),
+ {Opts,ERPid}.
+
+execute(Name, Opts, ERPid, Config) ->
+ ok = ct_test_support:run(Opts, Config),
+ Events = ct_test_support:get_events(ERPid, Config),
+
+ ct_test_support:log_events(Name,
+ reformat(Events, ?eh),
+ ?config(priv_dir, Config),
+ Opts),
+
+ TestEvents = events_to_check(Name),
+ ct_test_support:verify_events(TestEvents, Events, Config).
+
+reformat(Events, EH) ->
+ ct_test_support:reformat(Events, EH).
+
+%%%-----------------------------------------------------------------
+%%% TEST EVENTS
+%%%-----------------------------------------------------------------
+events_to_check(Test) ->
+ %% 2 tests (ct:run_test + script_start) is default
+ events_to_check(Test, 2).
+
+events_to_check(_, 0) ->
+ [];
+events_to_check(Test, N) ->
+ test_events(Test) ++ events_to_check(Test, N-1).
+
+
+test_events(_) ->
+ [
+ {?eh,tc_done,{io_test_SUITE,tc1,ok}},
+ {?eh,tc_done,{io_test_SUITE,tc2,ok}},
+ {?eh,tc_done,{io_test_SUITE,tc3,ok}},
+
+ {parallel,
+ [
+ {?eh,tc_start,{io_test_SUITE,tc1}},
+ {?eh,tc_start,{io_test_SUITE,tc2}},
+ {?eh,tc_start,{io_test_SUITE,tc3}},
+ {?eh,tc_done,{io_test_SUITE,tc1,ok}},
+ {?eh,tc_done,{io_test_SUITE,tc2,ok}},
+ {?eh,tc_done,{io_test_SUITE,tc3,ok}},
+ {parallel,
+ [
+ {?eh,tc_start,{io_test_SUITE,tc1}},
+ {?eh,tc_start,{io_test_SUITE,tc2}},
+ {?eh,tc_start,{io_test_SUITE,tc3}},
+ {?eh,tc_done,{io_test_SUITE,tc1,ok}},
+ {?eh,tc_done,{io_test_SUITE,tc2,ok}},
+ {?eh,tc_done,{io_test_SUITE,tc3,ok}},
+ {?eh,test_stats,{9,0,{0,0}}}
+ ]}
+ ]},
+
+ {?eh,test_done,{'DEF','STOP_TIME'}},
+ {?eh,stop_logging,[]}
+ ].
+
diff --git a/lib/common_test/test/ct_verbosity_SUITE_data/io_test_SUITE.erl b/lib/common_test/test/ct_verbosity_SUITE_data/io_test_SUITE.erl
new file mode 100644
index 0000000000..946e1c1989
--- /dev/null
+++ b/lib/common_test/test/ct_verbosity_SUITE_data/io_test_SUITE.erl
@@ -0,0 +1,156 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2009-2012. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(io_test_SUITE).
+
+-compile(export_all).
+
+-include_lib("common_test/include/ct.hrl").
+
+%%--------------------------------------------------------------------
+%% @spec suite() -> Info
+%% Info = [tuple()]
+%% @end
+%%--------------------------------------------------------------------
+suite() ->
+ [{timetrap,{seconds,10}}].
+
+%%--------------------------------------------------------------------
+%% @spec init_per_suite(Config0) ->
+%% Config1 | {skip,Reason} | {skip_and_save,Reason,Config1}
+%% Config0 = Config1 = [tuple()]
+%% Reason = term()
+%% @end
+%%--------------------------------------------------------------------
+init_per_suite(Config) ->
+ Config.
+
+%%--------------------------------------------------------------------
+%% @spec end_per_suite(Config0) -> void() | {save_config,Config1}
+%% Config0 = Config1 = [tuple()]
+%% @end
+%%--------------------------------------------------------------------
+end_per_suite(_Config) ->
+ ok.
+
+%%--------------------------------------------------------------------
+%% @spec init_per_group(GroupName, Config0) ->
+%% Config1 | {skip,Reason} | {skip_and_save,Reason,Config1}
+%% GroupName = atom()
+%% Config0 = Config1 = [tuple()]
+%% Reason = term()
+%% @end
+%%--------------------------------------------------------------------
+init_per_group(_GroupName, Config) ->
+ Config.
+
+%%--------------------------------------------------------------------
+%% @spec end_per_group(GroupName, Config0) ->
+%% void() | {save_config,Config1}
+%% GroupName = atom()
+%% Config0 = Config1 = [tuple()]
+%% @end
+%%--------------------------------------------------------------------
+end_per_group(_GroupName, _Config) ->
+ ok.
+
+%%--------------------------------------------------------------------
+%% @spec init_per_testcase(TestCase, Config0) ->
+%% Config1 | {skip,Reason} | {skip_and_save,Reason,Config1}
+%% TestCase = atom()
+%% Config0 = Config1 = [tuple()]
+%% Reason = term()
+%% @end
+%%--------------------------------------------------------------------
+init_per_testcase(_TestCase, Config) ->
+ Config.
+
+%%--------------------------------------------------------------------
+%% @spec end_per_testcase(TestCase, Config0) ->
+%% void() | {save_config,Config1} | {fail,Reason}
+%% TestCase = atom()
+%% Config0 = Config1 = [tuple()]
+%% Reason = term()
+%% @end
+%%--------------------------------------------------------------------
+end_per_testcase(_TestCase, _Config) ->
+ ok.
+
+%%--------------------------------------------------------------------
+%% @spec groups() -> [Group]
+%% Group = {GroupName,Properties,GroupsAndTestCases}
+%% GroupName = atom()
+%% Properties = [parallel | sequence | Shuffle | {RepeatType,N}]
+%% GroupsAndTestCases = [Group | {group,GroupName} | TestCase]
+%% TestCase = atom()
+%% Shuffle = shuffle | {shuffle,{integer(),integer(),integer()}}
+%% RepeatType = repeat | repeat_until_all_ok | repeat_until_all_fail |
+%% repeat_until_any_ok | repeat_until_any_fail
+%% N = integer() | forever
+%% @end
+%%--------------------------------------------------------------------
+groups() ->
+ [{g1, [parallel], [tc1,tc2,tc3,{group,g2}]},
+ {g2, [parallel], [tc1,tc2,tc3]}].
+
+%%--------------------------------------------------------------------
+%% @spec all() -> GroupsAndTestCases | {skip,Reason}
+%% GroupsAndTestCases = [{group,GroupName} | TestCase]
+%% GroupName = atom()
+%% TestCase = atom()
+%% Reason = term()
+%% @end
+%%--------------------------------------------------------------------
+all() ->
+ [tc1,tc2,tc3,{group,g1}].
+
+tc1(_C) ->
+ io:format("This is an io:format(~p)~n", [[]]),
+ ct:log("ct:log(default)", []),
+ ct:log(?STD_IMPORTANCE, "ct:log(default,~p)", [?STD_IMPORTANCE]),
+ ct:log(error, "ct:log(error)", []),
+ ct:log(error, ?STD_IMPORTANCE, "ct:log(error,~p)", [?STD_IMPORTANCE]),
+ ct:log(1, "ct:log(default,~p)", [1]),
+ ct:log(error, 1, "ct:log(error,~p)", [1]),
+ ct:log(99, "ct:log(default,~p)", [99]),
+ ct:log(error, 99, "ct:log(error,~p)", [99]),
+ ok.
+
+tc2(_C) ->
+ io:format("This is an io:format(~p)~n", [[]]),
+ ct:pal("ct:pal(default)", []),
+ ct:pal(?STD_IMPORTANCE, "ct:pal(default,~p)", [?STD_IMPORTANCE]),
+ ct:pal(error, "ct:pal(error)", []),
+ ct:pal(error, ?STD_IMPORTANCE, "ct:pal(error,~p)", [?STD_IMPORTANCE]),
+ ct:pal(1, "ct:pal(default,~p)", [1]),
+ ct:pal(error, 1, "ct:pal(error,~p)", [1]),
+ ct:pal(99, "ct:pal(default,~p)", [99]),
+ ct:pal(error, 99, "ct:pal(error,~p)", [99]),
+ ok.
+
+tc3(_C) ->
+ io:format("This is an io:format(~p)~n", [[]]),
+ ct:print("ct:print(default)", []),
+ ct:print(?STD_IMPORTANCE, "ct:print(default,~p)", [?STD_IMPORTANCE]),
+ ct:print(error, "ct:print(error)", []),
+ ct:print(error, ?STD_IMPORTANCE, "ct:print(error,~p)", [?STD_IMPORTANCE]),
+ ct:print(1, "ct:print(default,~p)", [1]),
+ ct:print(error, 1, "ct:print(error,~p)", [1]),
+ ct:print(99, "ct:print(default,~p)", [99]),
+ ct:print(error, 99, "ct:print(error,~p)", [99]),
+ ok.
diff --git a/lib/common_test/vsn.mk b/lib/common_test/vsn.mk
index b94f7f7593..877aa775fd 100644
--- a/lib/common_test/vsn.mk
+++ b/lib/common_test/vsn.mk
@@ -1 +1 @@
-COMMON_TEST_VSN = 1.6.1
+COMMON_TEST_VSN = 1.6.2
diff --git a/lib/compiler/test/compile_SUITE.erl b/lib/compiler/test/compile_SUITE.erl
index da53a6ba9c..2cd75944f4 100644
--- a/lib/compiler/test/compile_SUITE.erl
+++ b/lib/compiler/test/compile_SUITE.erl
@@ -242,6 +242,12 @@ makedep(Config) when is_list(Config) ->
[makedep,{makedep_output,Target}|IncludeOptions]),
?line {ok,Mf6} = file:read_file(Target),
?line BasicMf2 = makedep_canonicalize_result(Mf6, DataDir),
+ %% Rule with creating phony target.
+ ?line PhonyMfName = SimpleRootname ++ "-phony.mk",
+ ?line {ok,PhonyMf} = file:read_file(PhonyMfName),
+ ?line {ok,_,Mf7} = compile:file(Simple,
+ [binary,makedep,makedep_phony|IncludeOptions]),
+ ?line PhonyMf = makedep_canonicalize_result(Mf7, DataDir),
?line ok = file:delete(Target),
?line ok = file:del_dir(filename:dirname(Target)),
diff --git a/lib/compiler/test/compile_SUITE_data/simple-phony.mk b/lib/compiler/test/compile_SUITE_data/simple-phony.mk
new file mode 100644
index 0000000000..c84bcedd2a
--- /dev/null
+++ b/lib/compiler/test/compile_SUITE_data/simple-phony.mk
@@ -0,0 +1,3 @@
+simple.beam: $(srcdir)/simple.erl $(srcdir)/include/simple.hrl
+
+$(srcdir)/include/simple.hrl:
diff --git a/lib/erl_interface/aclocal.m4 b/lib/erl_interface/aclocal.m4
index 339a15a2bb..a76594d86f 100644
--- a/lib/erl_interface/aclocal.m4
+++ b/lib/erl_interface/aclocal.m4
@@ -59,6 +59,7 @@ AC_ARG_VAR(erl_xcomp_isysroot, [Absolute cross system root include path (only us
dnl Cross compilation variables
AC_ARG_VAR(erl_xcomp_bigendian, [big endian system: yes|no (only used when cross compiling)])
+AC_ARG_VAR(erl_xcomp_double_middle_endian, [double-middle-endian system: yes|no (only used when cross compiling)])
AC_ARG_VAR(erl_xcomp_linux_clock_gettime_correction, [clock_gettime() can be used for time correction: yes|no (only used when cross compiling)])
AC_ARG_VAR(erl_xcomp_linux_nptl, [have Native POSIX Thread Library: yes|no (only used when cross compiling)])
AC_ARG_VAR(erl_xcomp_linux_usable_sigusrx, [SIGUSR1 and SIGUSR2 can be used: yes|no (only used when cross compiling)])
@@ -606,6 +607,103 @@ ifelse([$5], , , [$5
fi
])
+dnl ----------------------------------------------------------------------
+dnl
+dnl AC_DOUBLE_MIDDLE_ENDIAN
+dnl
+dnl Checks whether doubles are represented in "middle-endian" format.
+dnl Sets ac_cv_double_middle_endian={no,yes,unknown} accordingly,
+dnl as well as DOUBLE_MIDDLE_ENDIAN.
+dnl
+dnl
+
+AC_DEFUN([AC_C_DOUBLE_MIDDLE_ENDIAN],
+[AC_CACHE_CHECK(whether double word ordering is middle-endian, ac_cv_c_double_middle_endian,
+[# It does not; compile a test program.
+AC_RUN_IFELSE(
+[AC_LANG_SOURCE([[#include <stdlib.h>
+
+int
+main(void)
+{
+ int i = 0;
+ int zero = 0;
+ int bigendian;
+ int zero_index = 0;
+
+ union
+ {
+ long int l;
+ char c[sizeof (long int)];
+ } u;
+
+ /* we'll use the one with 32-bit words */
+ union
+ {
+ double d;
+ unsigned int c[2];
+ } vint;
+
+ union
+ {
+ double d;
+ unsigned long c[2];
+ } vlong;
+
+ union
+ {
+ double d;
+ unsigned short c[2];
+ } vshort;
+
+
+ /* Are we little or big endian? From Harbison&Steele. */
+ u.l = 1;
+ bigendian = (u.c[sizeof (long int) - 1] == 1);
+
+ zero_index = bigendian ? 1 : 0;
+
+ vint.d = 1.0;
+ vlong.d = 1.0;
+ vshort.d = 1.0;
+
+ if (sizeof(unsigned int) == 4)
+ {
+ if (vint.c[zero_index] != 0)
+ zero = 1;
+ }
+ else if (sizeof(unsigned long) == 4)
+ {
+ if (vlong.c[zero_index] != 0)
+ zero = 1;
+ }
+ else if (sizeof(unsigned short) == 4)
+ {
+ if (vshort.c[zero_index] != 0)
+ zero = 1;
+ }
+
+ exit (zero);
+}
+]])],
+ [ac_cv_c_double_middle_endian=no],
+ [ac_cv_c_double_middle_endian=yes],
+ [ac_cv_c_double_middle=unknown])])
+case $ac_cv_c_double_middle_endian in
+ yes)
+ m4_default([$1],
+ [AC_DEFINE([DOUBLE_MIDDLE_ENDIAN], 1,
+ [Define to 1 if your processor stores the words in a double in
+ middle-endian format (like some ARMs).])]) ;;
+ no)
+ $2 ;;
+ *)
+ m4_default([$3],
+ [AC_MSG_WARN([unknown double endianness
+presetting ac_cv_c_double_middle_endian=no (or yes) will help])]) ;;
+esac
+])# AC_C_DOUBLE_MIDDLE_ENDIAN
+
dnl ----------------------------------------------------------------------
dnl
@@ -1337,6 +1435,14 @@ if test "$ac_cv_c_bigendian" = "yes"; then
AC_DEFINE(ETHR_BIGENDIAN, 1, [Define if bigendian])
fi
+case X$erl_xcomp_double_middle_endian in
+ X) ;;
+ Xyes|Xno|Xunknown) ac_cv_c_double_middle_endian=$erl_xcomp_double_middle_endian;;
+ *) AC_MSG_ERROR([Bad erl_xcomp_double_middle_endian value: $erl_xcomp_double_middle_endian]);;
+esac
+
+AC_C_DOUBLE_MIDDLE_ENDIAN
+
AC_ARG_ENABLE(native-ethr-impls,
AS_HELP_STRING([--disable-native-ethr-impls],
[disable native ethread implementations]),
diff --git a/lib/kernel/src/heart.erl b/lib/kernel/src/heart.erl
index 255ae4e51b..218be964a0 100644
--- a/lib/kernel/src/heart.erl
+++ b/lib/kernel/src/heart.erl
@@ -18,6 +18,10 @@
%%
-module(heart).
+-compile(no_native).
+% 'no_native' as part of a crude fix to make init:restart/0 work by clearing
+% all hipe inter-module information (hipe_mfa_info's in hipe_bif0.c).
+
%%%--------------------------------------------------------------------
%%% This is a rewrite of pre_heart from BS.3.
%%%
diff --git a/lib/kernel/src/hipe_unified_loader.erl b/lib/kernel/src/hipe_unified_loader.erl
index 8b3aa0286d..514c002d87 100644
--- a/lib/kernel/src/hipe_unified_loader.erl
+++ b/lib/kernel/src/hipe_unified_loader.erl
@@ -34,6 +34,13 @@
-module(hipe_unified_loader).
+-compile(no_native).
+% 'no_native' is a workaround to avoid "The code server called unloaded module"
+% caused by Mod:module_info(exports) in patch_to_emu_step1() called by post_beam_load.
+% Reproducable with hipelibs and asn1_SUITE.
+% I think the real solution would be to let BIF erlang:load_module/2 redirect all
+% hipe calls to the module and thereby remove post_beam_load.
+
-export([chunk_name/1,
%% Only the code and code_server modules may call the entries below!
load_native_code/2,
diff --git a/lib/kernel/src/inet.erl b/lib/kernel/src/inet.erl
index 0bb5444dbb..1a03424f88 100644
--- a/lib/kernel/src/inet.erl
+++ b/lib/kernel/src/inet.erl
@@ -763,8 +763,12 @@ sctp_opt([Opt|Opts], Mod, R, As) ->
{Name,Val} -> sctp_opt (Opts, Mod, R, As, Name, Val);
_ -> {error,badarg}
end;
-sctp_opt([], _Mod, R, _SockOpts) ->
- {ok, R}.
+sctp_opt([], _Mod, #sctp_opts{ifaddr=IfAddr}=R, _SockOpts) ->
+ if is_list(IfAddr) ->
+ {ok, R#sctp_opts{ifaddr=lists:reverse(IfAddr)}};
+ true ->
+ {ok, R}
+ end.
sctp_opt(Opts, Mod, R, As, Name, Val) ->
case add_opt(Name, Val, R#sctp_opts.opts, As) of
@@ -1015,11 +1019,7 @@ open(Fd, Addr, Port, Opts, Protocol, Family, Type, Module) when Fd < 0 ->
case prim_inet:setopts(S, Opts) of
ok ->
case if is_list(Addr) ->
- prim_inet:bind(S, add,
- [case A of
- {_,_} -> A;
- _ -> {A,Port}
- end || A <- Addr]);
+ bindx(S, Addr, Port);
true ->
prim_inet:bind(S, Addr, Port)
end of
@@ -1040,6 +1040,34 @@ open(Fd, Addr, Port, Opts, Protocol, Family, Type, Module) when Fd < 0 ->
open(Fd, _Addr, _Port, Opts, Protocol, Family, Type, Module) ->
fdopen(Fd, Opts, Protocol, Family, Type, Module).
+bindx(S, [Addr], Port0) ->
+ {IP, Port} = set_bindx_port(Addr, Port0),
+ prim_inet:bind(S, IP, Port);
+bindx(S, Addrs, Port0) ->
+ [{IP, Port} | Rest] = [set_bindx_port(Addr, Port0) || Addr <- Addrs],
+ case prim_inet:bind(S, IP, Port) of
+ {ok, AssignedPort} when Port =:= 0 ->
+ %% On newer Linux kernels, Solaris and FreeBSD, calling
+ %% bindx with port 0 is ok, but on SuSE 10, it results in einval
+ Rest2 = [change_bindx_0_port(Addr, AssignedPort) || Addr <- Rest],
+ prim_inet:bind(S, add, Rest2);
+ {ok, _} ->
+ prim_inet:bind(S, add, Rest);
+ Error ->
+ Error
+ end.
+
+set_bindx_port({_IP, _Port}=Addr, _OtherPort) ->
+ Addr;
+set_bindx_port(IP, Port) ->
+ {IP, Port}.
+
+change_bindx_0_port({IP, 0}, AssignedPort) ->
+ {IP, AssignedPort};
+change_bindx_0_port({_IP, _Port}=Addr, _AssignedPort) ->
+ Addr.
+
+
-spec fdopen(Fd :: non_neg_integer(),
Opts :: [socket_setopt()],
Protocol :: socket_protocol(),
diff --git a/lib/kernel/test/code_SUITE.erl b/lib/kernel/test/code_SUITE.erl
index 3e8bdaf1ff..827208b048 100644
--- a/lib/kernel/test/code_SUITE.erl
+++ b/lib/kernel/test/code_SUITE.erl
@@ -1550,7 +1550,8 @@ native_early_modules_1(Architecture) ->
true ->
?line true = lists:all(fun code:is_module_native/1,
[ets,file,filename,gb_sets,gb_trees,
- hipe_unified_loader,lists,os,packages]),
+ %%hipe_unified_loader, no_native as workaround
+ lists,os,packages]),
ok
end.
diff --git a/lib/kernel/test/gen_sctp_SUITE.erl b/lib/kernel/test/gen_sctp_SUITE.erl
index 8f490b6643..f4bf6e719e 100644
--- a/lib/kernel/test/gen_sctp_SUITE.erl
+++ b/lib/kernel/test/gen_sctp_SUITE.erl
@@ -31,14 +31,22 @@
[basic/1,
api_open_close/1,api_listen/1,api_connect_init/1,api_opts/1,
xfer_min/1,xfer_active/1,def_sndrcvinfo/1,implicit_inet6/1,
- basic_stream/1, xfer_stream_min/1, peeloff/1, buffers/1]).
+ basic_stream/1, xfer_stream_min/1, peeloff/1, buffers/1,
+ open_multihoming_ipv4_socket/1,
+ open_unihoming_ipv6_socket/1,
+ open_multihoming_ipv6_socket/1,
+ open_multihoming_ipv4_and_ipv6_socket/1]).
suite() -> [{ct_hooks,[ts_install_cth]}].
all() ->
[basic, api_open_close, api_listen, api_connect_init,
api_opts, xfer_min, xfer_active, def_sndrcvinfo, implicit_inet6,
- basic_stream, xfer_stream_min, peeloff, buffers].
+ basic_stream, xfer_stream_min, peeloff, buffers,
+ open_multihoming_ipv4_socket,
+ open_unihoming_ipv6_socket,
+ open_multihoming_ipv6_socket,
+ open_multihoming_ipv4_and_ipv6_socket].
groups() ->
[].
@@ -1105,6 +1113,192 @@ mk_data(N, Bytes, Bin) when N < Bytes ->
mk_data(_, _, Bin) ->
Bin.
+
+
+open_multihoming_ipv4_socket(doc) ->
+ "Test opening a multihoming ipv4 socket";
+open_multihoming_ipv4_socket(suite) ->
+ [];
+open_multihoming_ipv4_socket(Config) when is_list(Config) ->
+ ?line case get_addrs_by_family(inet, 2) of
+ {ok, [Addr1, Addr2]} ->
+ ?line do_open_and_connect([Addr1, Addr2], Addr1);
+ {error, Reason} ->
+ {skip, Reason}
+ end.
+
+open_unihoming_ipv6_socket(doc) ->
+ %% This test is mostly aimed to indicate
+ %% whether host has a non-working ipv6 setup
+ "Test opening a unihoming (non-multihoming) ipv6 socket";
+open_unihoming_ipv6_socket(suite) ->
+ [];
+open_unihoming_ipv6_socket(Config) when is_list(Config) ->
+ ?line case get_addrs_by_family(inet6, 1) of
+ {ok, [Addr]} ->
+ ?line do_open_and_connect([Addr], Addr);
+ {error, Reason} ->
+ {skip, Reason}
+ end.
+
+
+open_multihoming_ipv6_socket(doc) ->
+ "Test opening a multihoming ipv6 socket";
+open_multihoming_ipv6_socket(suite) ->
+ [];
+open_multihoming_ipv6_socket(Config) when is_list(Config) ->
+ ?line case get_addrs_by_family(inet6, 2) of
+ {ok, [Addr1, Addr2]} ->
+ ?line do_open_and_connect([Addr1, Addr2], Addr1);
+ {error, Reason} ->
+ {skip, Reason}
+ end.
+
+open_multihoming_ipv4_and_ipv6_socket(doc) ->
+ "Test opening a multihoming ipv6 socket with ipv4 and ipv6 addresses";
+open_multihoming_ipv4_and_ipv6_socket(suite) ->
+ [];
+open_multihoming_ipv4_and_ipv6_socket(Config) when is_list(Config) ->
+ ?line case get_addrs_by_family(inet_and_inet6, 2) of
+ {ok, [[InetAddr1, InetAddr2], [Inet6Addr1, Inet6Addr2]]} ->
+ %% Connect to the first address to test bind
+ ?line do_open_and_connect([InetAddr1, Inet6Addr1, InetAddr2],
+ InetAddr1),
+ ?line do_open_and_connect([Inet6Addr1, InetAddr1],
+ Inet6Addr1),
+
+ %% Connect an address, not the first,
+ %% to test sctp_bindx
+ ?line do_open_and_connect([Inet6Addr1, Inet6Addr2, InetAddr1],
+ Inet6Addr2),
+ ?line do_open_and_connect([Inet6Addr1, Inet6Addr2, InetAddr1],
+ InetAddr1);
+ {error, Reason} ->
+ {skip, Reason}
+ end.
+
+
+get_addrs_by_family(Family, NumAddrs) ->
+ case os:type() of
+ {unix,linux} ->
+ get_addrs_by_family_aux(Family, NumAddrs);
+ {unix,freebsd} ->
+ get_addrs_by_family_aux(Family, NumAddrs);
+ {unix,sunos} ->
+ case get_addrs_by_family_aux(Family, NumAddrs) of
+ {ok, [InetAddrs, Inet6Addrs]} when Family =:= inet_and_inet6 ->
+ %% Man page for sctp_bindx on Solaris says: "If sock is an
+ %% Internet Protocol Version 6 (IPv6) socket, addrs should
+ %% be an array of sockaddr_in6 structures containing IPv6
+ %% or IPv4-mapped IPv6 addresses."
+ {ok, [ipv4_map_addrs(InetAddrs), Inet6Addrs]};
+ {ok, Addrs} ->
+ {ok, Addrs};
+ {error, Reason} ->
+ {error, Reason}
+ end;
+ Os ->
+ Reason = if Family =:= inet_and_inet6 ->
+ f("Mixing ipv4 and ipv6 addresses for multihoming "
+ " has not been verified on ~p", [Os]);
+ true ->
+ f("Multihoming for ~p has not been verified on ~p",
+ [Family, Os])
+ end,
+ {error, Reason}
+ end.
+
+get_addrs_by_family_aux(Family, NumAddrs) when Family =:= inet;
+ Family =:= inet6 ->
+ ?line
+ case inet:getaddr(localhost, Family) of
+ {error,eafnosupport} ->
+ {skip, f("No support for ~p", Family)};
+ {ok, _} ->
+ ?line IfAddrs = ok(inet:getifaddrs()),
+ ?line case filter_addrs_by_family(IfAddrs, Family) of
+ Addrs when length(Addrs) >= NumAddrs ->
+ {ok, lists:sublist(Addrs, NumAddrs)};
+ [] ->
+ {error, f("Need ~p ~p address(es) found none~n",
+ [NumAddrs, Family])};
+ Addrs ->
+ {error,
+ f("Need ~p ~p address(es) found only ~p: ~p~n",
+ [NumAddrs, Family, length(Addrs), Addrs])}
+ end
+ end;
+get_addrs_by_family_aux(inet_and_inet6, NumAddrs) ->
+ ?line catch {ok, [case get_addrs_by_family_aux(Family, NumAddrs) of
+ {ok, Addrs} -> Addrs;
+ {error, Reason} -> throw({error, Reason})
+ end || Family <- [inet, inet6]]}.
+
+filter_addrs_by_family(IfAddrs, Family) ->
+ lists:flatten([[Addr || {addr, Addr} <- Info,
+ is_good_addr(Addr, Family)]
+ || {_IfName, Info} <- IfAddrs]).
+
+is_good_addr(Addr, inet) when tuple_size(Addr) =:= 4 ->
+ true;
+is_good_addr({0,0,0,0,0,16#ffff,_,_}, inet6) ->
+ false; %% ipv4 mapped
+is_good_addr({16#fe80,_,_,_,_,_,_,_}, inet6) ->
+ false; %% link-local
+is_good_addr(Addr, inet6) when tuple_size(Addr) =:= 8 ->
+ true;
+is_good_addr(_Addr, _Family) ->
+ false.
+
+ipv4_map_addrs(InetAddrs) ->
+ [begin
+ <<AB:16>> = <<A,B>>,
+ <<CD:16>> = <<C,D>>,
+ {0, 0, 0, 0, 0, 16#ffff, AB, CD}
+ end || {A,B,C,D} <- InetAddrs].
+
+f(F, A) ->
+ lists:flatten(io_lib:format(F, A)).
+
+do_open_and_connect(ServerAddresses, AddressToConnectTo) ->
+ ?line ServerFamily = get_family_by_addrs(ServerAddresses),
+ ?line io:format("Serving ~p addresses: ~p~n",
+ [ServerFamily, ServerAddresses]),
+ ?line S1 = ok(gen_sctp:open(0, [{ip,Addr} || Addr <- ServerAddresses] ++
+ [ServerFamily])),
+ ?line ok = gen_sctp:listen(S1, true),
+ ?line P1 = ok(inet:port(S1)),
+ ?line ClientFamily = get_family_by_addr(AddressToConnectTo),
+ ?line io:format("Connecting to ~p ~p~n",
+ [ClientFamily, AddressToConnectTo]),
+ ?line S2 = ok(gen_sctp:open(0, [ClientFamily])),
+ %% Verify client can connect
+ ?line #sctp_assoc_change{state=comm_up} =
+ ok(gen_sctp:connect(S2, AddressToConnectTo, P1, [])),
+ %% verify server side also receives comm_up from client
+ ?line recv_comm_up_eventually(S1),
+ ?line ok = gen_sctp:close(S2),
+ ?line ok = gen_sctp:close(S1).
+
+%% If at least one of the addresses is an ipv6 address, return inet6, else inet.
+get_family_by_addrs(Addresses) ->
+ ?line case lists:usort([get_family_by_addr(Addr) || Addr <- Addresses]) of
+ [inet, inet6] -> inet6;
+ [inet] -> inet;
+ [inet6] -> inet6
+ end.
+
+get_family_by_addr(Addr) when tuple_size(Addr) =:= 4 -> inet;
+get_family_by_addr(Addr) when tuple_size(Addr) =:= 8 -> inet6.
+
+recv_comm_up_eventually(S) ->
+ ?line case ok(gen_sctp:recv(S)) of
+ {_Addr, _Port, _Info, #sctp_assoc_change{state=comm_up}} ->
+ ok;
+ {_Addr, _Port, _Info, _OtherSctpMsg} ->
+ ?line recv_comm_up_eventually(S)
+ end.
+
%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%% socket gen_server ultra light
diff --git a/lib/mnesia/src/mnesia.appup.src b/lib/mnesia/src/mnesia.appup.src
index 304a15242f..355aafb215 100644
--- a/lib/mnesia/src/mnesia.appup.src
+++ b/lib/mnesia/src/mnesia.appup.src
@@ -1,6 +1,9 @@
%% -*- erlang -*-
{"%VSN%",
[
+ {"4.7.1", [{restart_application, mnesia}]},
+ {"4.7", [{restart_application, mnesia}]},
+ {"4.6", [{restart_application, mnesia}]},
{"4.5.1", [{restart_application, mnesia}]},
{"4.5", [{restart_application, mnesia}]},
{"4.4.19", [{restart_application, mnesia}]},
@@ -9,6 +12,9 @@
{"4.4.16", [{restart_application, mnesia}]}
],
[
+ {"4.7.1", [{restart_application, mnesia}]},
+ {"4.7", [{restart_application, mnesia}]},
+ {"4.6", [{restart_application, mnesia}]},
{"4.5.1", [{restart_application, mnesia}]},
{"4.5", [{restart_application, mnesia}]},
{"4.4.19", [{restart_application, mnesia}]},
diff --git a/lib/mnesia/src/mnesia_index.erl b/lib/mnesia/src/mnesia_index.erl
index 61210d7e55..37989a1958 100644
--- a/lib/mnesia/src/mnesia_index.erl
+++ b/lib/mnesia/src/mnesia_index.erl
@@ -120,9 +120,9 @@ del_object_bag(Tab, Key, Obj, Pos, Ixt, undefined) ->
IxKey = element(Pos, Obj),
Old = [X || X <- mnesia_lib:db_get(Tab, Key), element(Pos, X) =:= IxKey],
del_object_bag(Tab, Key, Obj, Pos, Ixt, Old);
-%% If Tab type is bag we need remove index identifier if Tab
-%% contains less than 2 elements.
-del_object_bag(_Tab, Key, Obj, Pos, Ixt, Old) when length(Old) < 2 ->
+%% If Tab type is bag we need remove index identifier if the object being
+%% deleted was the last one
+del_object_bag(_Tab, Key, Obj, Pos, Ixt, Old) when Old =:= [Obj] ->
del_ixes(Ixt, [Obj], Pos, Key);
del_object_bag(_Tab, _Key, _Obj, _Pos, _Ixt, _Old) -> ok.
diff --git a/lib/mnesia/test/mnesia_dirty_access_test.erl b/lib/mnesia/test/mnesia_dirty_access_test.erl
index abbdab48c0..a57adefbac 100644
--- a/lib/mnesia/test/mnesia_dirty_access_test.erl
+++ b/lib/mnesia/test/mnesia_dirty_access_test.erl
@@ -527,6 +527,9 @@ dirty_index_update_bag(Config, Storage) ->
?match(ok, mnesia:dirty_write(Rec1)),
?match([Rec1], mnesia:dirty_index_read(Tab, 2, ValPos)),
+ ?match(ok, mnesia:dirty_delete_object(Rec5)),
+ ?match([Rec1], mnesia:dirty_index_read(Tab, 2, ValPos)),
+
?match({atomic, ok}, mnesia:transaction(fun() -> mnesia:write(Rec2) end)),
R1 = mnesia:dirty_index_read(Tab, 2, ValPos),
?match([Rec1, Rec2], lists:sort(R1)),
diff --git a/lib/mnesia/test/mnesia_trans_access_test.erl b/lib/mnesia/test/mnesia_trans_access_test.erl
index c040d0ca3f..73c3fe0362 100644
--- a/lib/mnesia/test/mnesia_trans_access_test.erl
+++ b/lib/mnesia/test/mnesia_trans_access_test.erl
@@ -896,6 +896,10 @@ index_update_bag(Config)when is_list(Config) ->
?match({atomic, [Rec1]},
mnesia:transaction(fun() -> mnesia:index_read(Tab, 2, ValPos) end)),
+ ?match({atomic, ok}, mnesia:transaction(fun() -> mnesia:delete_object(Rec5) end)),
+ ?match({atomic, [Rec1]},
+ mnesia:transaction(fun() -> mnesia:index_read(Tab, 2, ValPos) end)),
+
?match({atomic, ok},
mnesia:transaction(fun() -> mnesia:write(Rec2) end)),
{atomic, R1} = mnesia:transaction(fun() -> mnesia:index_read(Tab, 2, ValPos) end),
diff --git a/lib/observer/src/observer.app.src b/lib/observer/src/observer.app.src
index 5c65ea5c8f..d3aaf351dd 100644
--- a/lib/observer/src/observer.app.src
+++ b/lib/observer/src/observer.app.src
@@ -25,6 +25,18 @@
etop_gui,
etop_tr,
etop_txt,
+ observer,
+ observer_app_wx,
+ observer_lib,
+ observer_perf_wx,
+ observer_pro_wx,
+ observer_procinfo,
+ observer_sys_wx,
+ observer_trace_wx,
+ observer_traceoptions_wx,
+ observer_tv_table,
+ observer_tv_wx,
+ observer_wx,
ttb,
ttb_et]},
{registered, []},
diff --git a/lib/observer/src/observer_tv_table.erl b/lib/observer/src/observer_tv_table.erl
index 8fdcbf331c..8eeffb7f91 100644
--- a/lib/observer/src/observer_tv_table.erl
+++ b/lib/observer/src/observer_tv_table.erl
@@ -324,7 +324,7 @@ handle_event(#wx{id=?SEARCH_ENTRY, event=#wxCommand{type=command_text_enter,cmdS
wxStatusBar:setStatusText(SB, "Not found"),
Pid ! {mark_search_hit, Find#find.start},
wxListCtrl:refreshItem(Grid, Find#find.start),
- {noreply, State#state{search=Search#search{find=#find{found=false}}}};
+ {noreply, State#state{search=Search#search{find=Find#find{found=false}}}};
Row ->
wxListCtrl:ensureVisible(Grid, Row),
wxListCtrl:refreshItem(Grid, Row),
@@ -616,7 +616,7 @@ search([Str, Row, Dir0, CaseSens],
search(Row, Dir, Re, Table) ->
Res = try lists:nth(Row+1, Table) of
- Term ->
+ [Term|_] ->
Str = format(Term),
re:run(Str, Re)
catch _:_ -> no_more
@@ -772,7 +772,7 @@ format_tuple(_Tuple, 1, 0) ->
format_list([]) -> "[]";
format_list(List) ->
case printable_list(List) of
- true -> io_lib:format("\"~ts\"", [List]);
+ true -> io_lib:format("\"~ts\"", [map_printable_list(List)]);
false -> [$[ | make_list(List)]
end.
@@ -781,6 +781,24 @@ make_list([Last]) ->
make_list([Head|Tail]) ->
[format(Head), $,|make_list(Tail)].
+map_printable_list([$\n|Cs]) ->
+ [$\\, $n|map_printable_list(Cs)];
+map_printable_list([$\r|Cs]) ->
+ [$\\, $r|map_printable_list(Cs)];
+map_printable_list([$\t|Cs]) ->
+ [$\\, $t|map_printable_list(Cs)];
+map_printable_list([$\v|Cs]) ->
+ [$\\, $v|map_printable_list(Cs)];
+map_printable_list([$\b|Cs]) ->
+ [$\\, $b|map_printable_list(Cs)];
+map_printable_list([$\f|Cs]) ->
+ [$\\, $f|map_printable_list(Cs)];
+map_printable_list([$\e|Cs]) ->
+ [$\\, $e|map_printable_list(Cs)];
+map_printable_list([]) -> [];
+map_printable_list([C|Cs]) ->
+ [C|map_printable_list(Cs)].
+
%% printable_list([Char]) -> bool()
%% Return true if CharList is a list of printable characters, else
%% false.
diff --git a/lib/odbc/aclocal.m4 b/lib/odbc/aclocal.m4
index 339a15a2bb..a76594d86f 100644
--- a/lib/odbc/aclocal.m4
+++ b/lib/odbc/aclocal.m4
@@ -59,6 +59,7 @@ AC_ARG_VAR(erl_xcomp_isysroot, [Absolute cross system root include path (only us
dnl Cross compilation variables
AC_ARG_VAR(erl_xcomp_bigendian, [big endian system: yes|no (only used when cross compiling)])
+AC_ARG_VAR(erl_xcomp_double_middle_endian, [double-middle-endian system: yes|no (only used when cross compiling)])
AC_ARG_VAR(erl_xcomp_linux_clock_gettime_correction, [clock_gettime() can be used for time correction: yes|no (only used when cross compiling)])
AC_ARG_VAR(erl_xcomp_linux_nptl, [have Native POSIX Thread Library: yes|no (only used when cross compiling)])
AC_ARG_VAR(erl_xcomp_linux_usable_sigusrx, [SIGUSR1 and SIGUSR2 can be used: yes|no (only used when cross compiling)])
@@ -606,6 +607,103 @@ ifelse([$5], , , [$5
fi
])
+dnl ----------------------------------------------------------------------
+dnl
+dnl AC_DOUBLE_MIDDLE_ENDIAN
+dnl
+dnl Checks whether doubles are represented in "middle-endian" format.
+dnl Sets ac_cv_double_middle_endian={no,yes,unknown} accordingly,
+dnl as well as DOUBLE_MIDDLE_ENDIAN.
+dnl
+dnl
+
+AC_DEFUN([AC_C_DOUBLE_MIDDLE_ENDIAN],
+[AC_CACHE_CHECK(whether double word ordering is middle-endian, ac_cv_c_double_middle_endian,
+[# It does not; compile a test program.
+AC_RUN_IFELSE(
+[AC_LANG_SOURCE([[#include <stdlib.h>
+
+int
+main(void)
+{
+ int i = 0;
+ int zero = 0;
+ int bigendian;
+ int zero_index = 0;
+
+ union
+ {
+ long int l;
+ char c[sizeof (long int)];
+ } u;
+
+ /* we'll use the one with 32-bit words */
+ union
+ {
+ double d;
+ unsigned int c[2];
+ } vint;
+
+ union
+ {
+ double d;
+ unsigned long c[2];
+ } vlong;
+
+ union
+ {
+ double d;
+ unsigned short c[2];
+ } vshort;
+
+
+ /* Are we little or big endian? From Harbison&Steele. */
+ u.l = 1;
+ bigendian = (u.c[sizeof (long int) - 1] == 1);
+
+ zero_index = bigendian ? 1 : 0;
+
+ vint.d = 1.0;
+ vlong.d = 1.0;
+ vshort.d = 1.0;
+
+ if (sizeof(unsigned int) == 4)
+ {
+ if (vint.c[zero_index] != 0)
+ zero = 1;
+ }
+ else if (sizeof(unsigned long) == 4)
+ {
+ if (vlong.c[zero_index] != 0)
+ zero = 1;
+ }
+ else if (sizeof(unsigned short) == 4)
+ {
+ if (vshort.c[zero_index] != 0)
+ zero = 1;
+ }
+
+ exit (zero);
+}
+]])],
+ [ac_cv_c_double_middle_endian=no],
+ [ac_cv_c_double_middle_endian=yes],
+ [ac_cv_c_double_middle=unknown])])
+case $ac_cv_c_double_middle_endian in
+ yes)
+ m4_default([$1],
+ [AC_DEFINE([DOUBLE_MIDDLE_ENDIAN], 1,
+ [Define to 1 if your processor stores the words in a double in
+ middle-endian format (like some ARMs).])]) ;;
+ no)
+ $2 ;;
+ *)
+ m4_default([$3],
+ [AC_MSG_WARN([unknown double endianness
+presetting ac_cv_c_double_middle_endian=no (or yes) will help])]) ;;
+esac
+])# AC_C_DOUBLE_MIDDLE_ENDIAN
+
dnl ----------------------------------------------------------------------
dnl
@@ -1337,6 +1435,14 @@ if test "$ac_cv_c_bigendian" = "yes"; then
AC_DEFINE(ETHR_BIGENDIAN, 1, [Define if bigendian])
fi
+case X$erl_xcomp_double_middle_endian in
+ X) ;;
+ Xyes|Xno|Xunknown) ac_cv_c_double_middle_endian=$erl_xcomp_double_middle_endian;;
+ *) AC_MSG_ERROR([Bad erl_xcomp_double_middle_endian value: $erl_xcomp_double_middle_endian]);;
+esac
+
+AC_C_DOUBLE_MIDDLE_ENDIAN
+
AC_ARG_ENABLE(native-ethr-impls,
AS_HELP_STRING([--disable-native-ethr-impls],
[disable native ethread implementations]),
diff --git a/lib/odbc/c_src/odbcserver.c b/lib/odbc/c_src/odbcserver.c
index ab2d7fe210..6d4460014f 100644
--- a/lib/odbc/c_src/odbcserver.c
+++ b/lib/odbc/c_src/odbcserver.c
@@ -176,7 +176,7 @@ static void encode_column_dyn(db_column column, int column_nr,
static void encode_data_type(SQLSMALLINT sql_type, SQLINTEGER size,
SQLSMALLINT decimal_digits, db_state *state);
static Boolean decode_params(db_state *state, byte *buffer, int *index, param_array **params,
- int i, int j);
+ int i, int j, int num_param_values);
/*------------- Erlang port communication functions ----------------------*/
@@ -212,6 +212,7 @@ static db_column * alloc_column_buffer(int n);
static void free_column_buffer(db_column **columns, int n);
static void free_params(param_array **params, int cols);
static void clean_state(db_state *state);
+static SQLLEN* alloc_strlen_indptr(int n, int val);
/* ------------- Init/map/bind/retrive functions -------------------------*/
@@ -1157,7 +1158,7 @@ static db_result_msg encode_out_params(db_state *state,
break;
case SQL_C_BIT:
ei_x_encode_atom(&dynamic_buffer(state),
- ((Boolean*)values)[j]==TRUE?"true":"false");
+ ((byte*)values)[j]==TRUE?"true":"false");
break;
default:
ei_x_encode_atom(&dynamic_buffer(state), "error");
@@ -1579,37 +1580,48 @@ static void encode_data_type(SQLSMALLINT sql_type, SQLINTEGER size,
}
static Boolean decode_params(db_state *state, byte *buffer, int *index, param_array **params,
- int i, int j)
+ int i, int j, int num_param_values)
{
int erl_type, size;
long bin_size, l64;
long val;
param_array* param;
TIMESTAMP_STRUCT* ts;
+ char atomarray[MAXATOMLEN+1];
ei_get_type(buffer, index, &erl_type, &size);
param = &(*params)[i];
+ if(erl_type == ERL_ATOM_EXT) {
+ ei_decode_atom(buffer, index, atomarray);
+ if(strncmp(atomarray, "null", 4) == 0 ) {
+ param->offset += param->type.len;
+
+ if(!param->type.strlen_or_indptr_array)
+ param->type.strlen_or_indptr_array = alloc_strlen_indptr(num_param_values, param->type.len);
+
+ param->type.strlen_or_indptr_array[j] = SQL_NULL_DATA;
+ return TRUE;
+ }
+ }
+
switch (param->type.c) {
case SQL_C_CHAR:
if (binary_strings(state)) {
ei_decode_binary(buffer, index,
&(param->values.string[param->offset]), &bin_size);
param->offset += param->type.len;
- param->type.strlen_or_indptr_array[j] = SQL_NTS;
} else {
if(erl_type != ERL_STRING_EXT) {
return FALSE;
}
ei_decode_string(buffer, index, &(param->values.string[param->offset]));
param->offset += param->type.len;
- param->type.strlen_or_indptr_array[j] = SQL_NTS;
}
break;
case SQL_C_WCHAR:
ei_decode_binary(buffer, index, &(param->values.string[param->offset]), &bin_size);
param->offset += param->type.len;
- param->type.strlen_or_indptr_array[j] = SQL_NTS;
break;
case SQL_C_TYPE_TIMESTAMP:
ts = (TIMESTAMP_STRUCT*) param->values.string;
@@ -1661,9 +1673,13 @@ static Boolean decode_params(db_state *state, byte *buffer, int *index, param_ar
if((erl_type != ERL_ATOM_EXT)) {
return FALSE;
}
- ei_decode_boolean(buffer, index, &(param->values.bool[j]));
+ if (strncmp((char*)atomarray,"true",4) == 0)
+ param->values.bool[j] = TRUE;
+ else if (strncmp((char*)atomarray,"false",5) == 0)
+ param->values.bool[j] = FALSE;
+ else
+ return -1;
break;
-
default:
return FALSE;
}
@@ -2014,6 +2030,18 @@ static void clean_state(db_state *state)
nr_of_columns(state) = 0;
}
+/* Allocates and fill with default value StrLen_or_IndPtr array */
+static SQLLEN* alloc_strlen_indptr(int n, int val)
+{
+ int i;
+ SQLLEN* arr = (SQLLEN*)safe_malloc(n * sizeof(SQLLEN));
+
+ for( i=0; i < n; ++i )
+ arr[i] = val;
+
+ return arr;
+}
+
/* ------------- Init/map/bind/retrive functions ------------------------*/
/* Prepare the state for a connection */
@@ -2118,7 +2146,7 @@ static void init_param_column(param_array *params, byte *buffer, int *index,
(double *)safe_malloc(num_param_values * params->type.len);
} else if(params->type.c == SQL_C_CHAR) {
params->type.strlen_or_indptr_array
- = (SQLLEN*)safe_malloc(num_param_values * sizeof(SQLINTEGER));
+ = alloc_strlen_indptr(num_param_values, SQL_NTS);
params->values.string =
(byte *)safe_malloc(num_param_values *
sizeof(byte)* params->type.len);
@@ -2136,8 +2164,8 @@ static void init_param_column(param_array *params, byte *buffer, int *index,
params->type.len = length+1;
params->type.c = SQL_C_CHAR;
params->type.col_size = (SQLUINTEGER)length;
- params->type.strlen_or_indptr_array =
- (SQLLEN*)safe_malloc(num_param_values * sizeof(SQLINTEGER));
+ params->type.strlen_or_indptr_array
+ = alloc_strlen_indptr(num_param_values, SQL_NTS);
params->values.string =
(byte *)safe_malloc(num_param_values *
sizeof(byte)* params->type.len);
@@ -2159,8 +2187,8 @@ static void init_param_column(param_array *params, byte *buffer, int *index,
params->type.len = (length+1)*sizeof(SQLWCHAR);
params->type.c = SQL_C_WCHAR;
params->type.col_size = (SQLUINTEGER)length;
- params->type.strlen_or_indptr_array =
- (SQLLEN*)safe_malloc(num_param_values * sizeof(SQLINTEGER));
+ params->type.strlen_or_indptr_array
+ = alloc_strlen_indptr(num_param_values, SQL_NTS);
params->values.string =
(byte *)safe_malloc(num_param_values * sizeof(byte) * params->type.len);
@@ -2201,10 +2229,10 @@ static void init_param_column(param_array *params, byte *buffer, int *index,
case USER_BOOLEAN:
params->type.sql = SQL_BIT;
params->type.c = SQL_C_BIT;
- params->type.len = sizeof(Boolean);
+ params->type.len = sizeof(byte);
params->type.col_size = params->type.len;
params->values.bool =
- (Boolean *)safe_malloc(num_param_values * params->type.len);
+ (byte *)safe_malloc(num_param_values * params->type.len);
break;
}
params->offset = 0;
@@ -2411,7 +2439,7 @@ static param_array * bind_parameter_arrays(byte *buffer, int *index,
}
for (j = 0; j < num_param_values; j++) {
- if(!decode_params(state, buffer, index, &params, i, j)) {
+ if(!decode_params(state, buffer, index, &params, i, j, num_param_values)) {
/* An input parameter was not of the expected type */
free_params(&params, i);
return params;
diff --git a/lib/odbc/c_src/odbcserver.h b/lib/odbc/c_src/odbcserver.h
index 56b6148777..a76cedf1af 100644
--- a/lib/odbc/c_src/odbcserver.h
+++ b/lib/odbc/c_src/odbcserver.h
@@ -156,7 +156,7 @@ typedef struct {
byte *string;
SQLINTEGER *integer;
double *floating;
- Boolean *bool;
+ byte *bool;
}values;
} param_array;
diff --git a/lib/odbc/src/odbc.appup.src b/lib/odbc/src/odbc.appup.src
index 853323da09..c7c83ea079 100644
--- a/lib/odbc/src/odbc.appup.src
+++ b/lib/odbc/src/odbc.appup.src
@@ -1,12 +1,8 @@
%% -*- erlang -*-
{"%VSN%",
[
- {"2.10.11", [{restart_application, odbc}]},
- {"2.10.10", [{restart_application, odbc}]},
- {"2.10.9", [{restart_application, odbc}]}
+ {<<"2\\.*">>, [{restart_application, odbc}]}
],
[
- {"2.10.11", [{restart_application, odbc}]},
- {"2.10.10", [{restart_application, odbc}]},
- {"2.10.9", [{restart_application, odbc}]}
+ {<<"2\\.*">>, [{restart_application, odbc}]}
]}.
diff --git a/lib/odbc/src/odbc.erl b/lib/odbc/src/odbc.erl
index 9f7b06dcf1..9633b85cb2 100644
--- a/lib/odbc/src/odbc.erl
+++ b/lib/odbc/src/odbc.erl
@@ -755,7 +755,10 @@ handle_info({'DOWN', _Ref, _Type, _Process, shutdown}, State) ->
handle_info({'DOWN', _Ref, _Type, Process, Reason}, State) ->
{stop, {stopped, {'EXIT', Process, Reason}},
State#state{reply_to = undefined}};
-
+
+handle_info({tcp_closed, Socket}, State = #state{odbc_socket=Socket,
+ state = disconnecting}) ->
+ {stop, normal, State};
%---------------------------------------------------------------------------
%% Catch all - throws away unknown messages (This could happen by "accident"
%% so we do not want to crash, but we make a log entry as it is an
@@ -942,9 +945,11 @@ fix_params({sql_bit, InOut, Values}) ->
fix_params({'sql_timestamp', InOut, Values}) ->
NewValues =
case (catch
- lists:map(fun({{Year,Month,Day},{Hour,Minute,Second}}) ->
- {Year,Month,Day,Hour,Minute,Second}
- end, Values)) of
+ lists:map(
+ fun({{Year,Month,Day},{Hour,Minute,Second}}) ->
+ {Year,Month,Day,Hour,Minute,Second};
+ (null) -> null
+ end, Values)) of
Result ->
Result
end,
@@ -960,15 +965,15 @@ fix_inout(out) ->
fix_inout(inout) ->
?INOUT.
-string_terminate([Value| _ ] = Values) when is_list(Value)->
- case (catch
- lists:map(fun(Str) -> Str ++ [?STR_TERMINATOR] end, Values)) of
- Result ->
- Result
- end;
-string_terminate([Value| _ ] = Values) when is_binary(Value)->
- case (catch
- lists:map(fun(B) -> <<B/binary,0:16>> end, Values)) of
+string_terminate(Values) ->
+ case (catch lists:map(fun string_terminate_value/1, Values)) of
Result ->
Result
end.
+
+string_terminate_value(String) when is_list(String) ->
+ String ++ [?STR_TERMINATOR];
+string_terminate_value(Binary) when is_binary(Binary) ->
+ <<Binary/binary,0:16>>;
+string_terminate_value(null) ->
+ null.
diff --git a/lib/odbc/vsn.mk b/lib/odbc/vsn.mk
index fb6e208a52..3bb2fe5bce 100644
--- a/lib/odbc/vsn.mk
+++ b/lib/odbc/vsn.mk
@@ -1 +1 @@
-ODBC_VSN = 2.10.12
+ODBC_VSN = 2.10.13
diff --git a/lib/ssh/test/ssh_basic_SUITE.erl b/lib/ssh/test/ssh_basic_SUITE.erl
index c5019425cd..2ceaa9daa5 100644
--- a/lib/ssh/test/ssh_basic_SUITE.erl
+++ b/lib/ssh/test/ssh_basic_SUITE.erl
@@ -194,10 +194,10 @@ misc_ssh_options(Config) when is_list(Config) ->
SystemDir = filename:join(?config(priv_dir, Config), system),
UserDir = ?config(priv_dir, Config),
- CMiscOpt0 = [{connecect_timeout, 1000}, {ip_v6_disable, false}, {user_dir, UserDir}],
- CMiscOpt1 = [{connecect_timeout, infinity}, {ip_v6_disable, true}, {user_dir, UserDir}],
- SMiscOpt0 = [{ip_v6_disable, false}, {user_dir, UserDir}, {system_dir, SystemDir}],
- SMiscOpt1 = [{ip_v6_disable, true}, {user_dir, UserDir}, {system_dir, SystemDir}],
+ CMiscOpt0 = [{connecect_timeout, 1000}, {ip_v6_disabled, false}, {user_dir, UserDir}],
+ CMiscOpt1 = [{connecect_timeout, infinity}, {ip_v6_disabled, true}, {user_dir, UserDir}],
+ SMiscOpt0 = [{ip_v6_disabled, false}, {user_dir, UserDir}, {system_dir, SystemDir}],
+ SMiscOpt1 = [{ip_v6_disabled, true}, {user_dir, UserDir}, {system_dir, SystemDir}],
ClientOpts = ?config(client_opts, Config),
ServerOpts = ?config(server_opts, Config),
diff --git a/lib/syntax_tools/doc/overview.edoc b/lib/syntax_tools/doc/overview.edoc
index 23eadce8fe..df02ad0b3a 100644
--- a/lib/syntax_tools/doc/overview.edoc
+++ b/lib/syntax_tools/doc/overview.edoc
@@ -1,5 +1,9 @@
+ -*- html -*-
-@author Richard Carlsson <[email protected]>
+ Syntax Tools overview page
+
+
+@author Richard Carlsson <[email protected]>
@copyright 1997-2004 Richard Carlsson
@version {@version}
@title Erlang Syntax Tools
diff --git a/lib/syntax_tools/src/epp_dodger.erl b/lib/syntax_tools/src/epp_dodger.erl
index 9f6f7d815e..b3ced34c14 100644
--- a/lib/syntax_tools/src/epp_dodger.erl
+++ b/lib/syntax_tools/src/epp_dodger.erl
@@ -14,10 +14,8 @@
%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
%% USA
%%
-%% $Id$
-%%
%% @copyright 2001-2006 Richard Carlsson
-%% @author Richard Carlsson <[email protected]>
+%% @author Richard Carlsson <[email protected]>
%% @end
%% =====================================================================
diff --git a/lib/syntax_tools/src/erl_comment_scan.erl b/lib/syntax_tools/src/erl_comment_scan.erl
index 108ab3bffd..b833e1c069 100644
--- a/lib/syntax_tools/src/erl_comment_scan.erl
+++ b/lib/syntax_tools/src/erl_comment_scan.erl
@@ -16,7 +16,7 @@
%%
%% =====================================================================
%% @copyright 1997-2006 Richard Carlsson
-%% @author Richard Carlsson <[email protected]>
+%% @author Richard Carlsson <[email protected]>
%% @end
%% =====================================================================
diff --git a/lib/syntax_tools/src/erl_prettypr.erl b/lib/syntax_tools/src/erl_prettypr.erl
index 7caf0b3db6..f4bbf975c3 100644
--- a/lib/syntax_tools/src/erl_prettypr.erl
+++ b/lib/syntax_tools/src/erl_prettypr.erl
@@ -14,10 +14,8 @@
%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
%% USA
%%
-%% $Id$
-%%
%% @copyright 1997-2006 Richard Carlsson
-%% @author Richard Carlsson <[email protected]>
+%% @author Richard Carlsson <[email protected]>
%% @end
%% =====================================================================
diff --git a/lib/syntax_tools/src/erl_recomment.erl b/lib/syntax_tools/src/erl_recomment.erl
index fc7c515700..7b2f9f7adb 100644
--- a/lib/syntax_tools/src/erl_recomment.erl
+++ b/lib/syntax_tools/src/erl_recomment.erl
@@ -14,10 +14,8 @@
%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
%% USA
%%
-%% $Id$
-%%
%% @copyright 1997-2006 Richard Carlsson
-%% @author Richard Carlsson <[email protected]>
+%% @author Richard Carlsson <[email protected]>
%% @end
%% =====================================================================
diff --git a/lib/syntax_tools/src/erl_syntax.erl b/lib/syntax_tools/src/erl_syntax.erl
index 32fd3722d6..76a6a6dc36 100644
--- a/lib/syntax_tools/src/erl_syntax.erl
+++ b/lib/syntax_tools/src/erl_syntax.erl
@@ -14,10 +14,8 @@
%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
%% USA
%%
-%% $Id$
-%%
%% @copyright 1997-2006 Richard Carlsson
-%% @author Richard Carlsson <[email protected]>
+%% @author Richard Carlsson <[email protected]>
%% @end
%% =====================================================================
@@ -26,23 +24,20 @@
%% This module defines an abstract data type for representing Erlang
%% source code as syntax trees, in a way that is backwards compatible
%% with the data structures created by the Erlang standard library
-%% parser module <code>erl_parse</code> (often referred to as "parse
+%% parser module `erl_parse' (often referred to as "parse
%% trees", which is a bit of a misnomer). This means that all
-%% <code>erl_parse</code> trees are valid abstract syntax trees, but the
+%% `erl_parse' trees are valid abstract syntax trees, but the
%% reverse is not true: abstract syntax trees can in general not be used
-%% as input to functions expecting an <code>erl_parse</code> tree.
+%% as input to functions expecting an `erl_parse' tree.
%% However, as long as an abstract syntax tree represents a correct
-%% Erlang program, the function <a
-%% href="#revert-1"><code>revert/1</code></a> should be able to
-%% transform it to the corresponding <code>erl_parse</code>
+%% Erlang program, the function {@link revert/1} should be able to
+%% transform it to the corresponding `erl_parse'
%% representation.
%%
-%% A recommended starting point for the first-time user is the
-%% documentation of the <a
-%% href="#type-syntaxTree"><code>syntaxTree()</code></a> data type, and
-%% the function <a href="#type-1"><code>type/1</code></a>.
+%% A recommended starting point for the first-time user is the documentation
+%% of the {@link syntaxTree()} data type, and the function {@link type/1}.
%%
-%% <h3><b>NOTES:</b></h3>
+%% == NOTES: ==
%%
%% This module deals with the composition and decomposition of
%% <em>syntactic</em> entities (as opposed to semantic ones); its
@@ -52,36 +47,31 @@
%% in general, the user is assumed to pass type-correct arguments - if
%% this is not done, the effects are not defined.
%%
-%% With the exception of the <code>erl_parse</code> data structures,
+%% With the exception of the {@link erl_parse()} data structures,
%% the internal representations of abstract syntax trees are subject to
%% change without notice, and should not be documented outside this
%% module. Furthermore, we do not give any guarantees on how an abstract
%% syntax tree may or may not be represented, <em>with the following
%% exceptions</em>: no syntax tree is represented by a single atom, such
-%% as <code>none</code>, by a list constructor <code>[X | Y]</code>, or
-%% by the empty list <code>[]</code>. This can be relied on when writing
+%% as `none', by a list constructor `[X | Y]', or
+%% by the empty list `[]'. This can be relied on when writing
%% functions that operate on syntax trees.
-%% @type syntaxTree(). An abstract syntax tree. The
-%% <code>erl_parse</code> "parse tree" representation is a subset of the
-%% <code>syntaxTree()</code> representation.
+%% @type syntaxTree(). An abstract syntax tree. The {@link erl_parse()}
+%% "parse tree" representation is a proper subset of the `syntaxTree()'
+%% representation.
%%
%% Every abstract syntax tree node has a <em>type</em>, given by the
-%% function <a href="#type-1"><code>type/1</code></a>. Each node also
-%% has associated <em>attributes</em>; see <a
-%% href="#get_attrs-1"><code>get_attrs/1</code></a> for details. The
-%% functions <a href="#make_tree-2"><code>make_tree/2</code></a> and <a
-%% href="#subtrees-1"><code>subtrees/1</code></a> are generic
+%% function {@link type/1}. Each node also has associated
+%% <em>attributes</em>; see {@link get_attrs/1} for details. The functions
+%% {@link make_tree/2} and {@link subtrees/1} are generic
%% constructor/decomposition functions for abstract syntax trees. The
-%% functions <a href="#abstract-1"><code>abstract/1</code></a> and <a
-%% href="#concrete-1"><code>concrete/1</code></a> convert between
+%% functions {@link abstract/1} and {@link concrete/1} convert between
%% constant Erlang terms and their syntactic representations. The set of
-%% syntax tree nodes is extensible through the <a
-%% href="#tree-2"><code>tree/2</code></a> function.
+%% syntax tree nodes is extensible through the {@link tree/2} function.
%%
-%% A syntax tree can be transformed to the <code>erl_parse</code>
-%% representation with the <a href="#revert-1"><code>revert/1</code></a>
-%% function.
+%% A syntax tree can be transformed to the {@link erl_parse()}
+%% representation with the {@link revert/1} function.
-module(erl_syntax).
@@ -309,7 +299,7 @@
data/1,
is_tree/1]).
--export_type([forms/0, syntaxTree/0, syntaxTreeAttributes/0]).
+-export_type([forms/0, syntaxTree/0, syntaxTreeAttributes/0, padding/0]).
%% =====================================================================
%% IMPLEMENTATION NOTES:
@@ -390,11 +380,15 @@
-record(wrapper, {type :: atom(),
attr = #attr{} :: #attr{},
- tree :: term()}).
+ tree :: erl_parse()}).
%% =====================================================================
--type syntaxTree() :: #tree{} | #wrapper{} | tuple(). % XXX: refine
+-type syntaxTree() :: #tree{} | #wrapper{} | erl_parse().
+
+-type erl_parse() :: erl_parse:abstract_form() | erl_parse:abstract_expr().
+%% The representation built by the Erlang standard library parser
+%% `erl_parse'. This is a subset of the {@link syntaxTree()} type.
%% =====================================================================
%%
@@ -404,12 +398,11 @@
%% =====================================================================
-%% @spec type(Node::syntaxTree()) -> atom()
-%%
-%% @doc Returns the type tag of <code>Node</code>. If <code>Node</code>
+%% @doc Returns the type tag of `Node'. If `Node'
%% does not represent a syntax tree, evaluation fails with reason
-%% <code>badarg</code>. Node types currently defined by this module are:
-%% <p><center><table border="1">
+%% `badarg'. Node types currently defined by this module are:
+%%
+%% <center><table border="1">
%% <tr>
%% <td>application</td>
%% <td>arity_qualifier</td>
@@ -476,12 +469,13 @@
%% <td>variable</td>
%% <td>warning_marker</td>
%% </tr>
-%% </table></center></p>
-%% <p>The user may (for special purposes) create additional nodes
-%% with other type tags, using the <code>tree/2</code> function.</p>
+%% </table></center>
+%%
+%% The user may (for special purposes) create additional nodes
+%% with other type tags, using the {@link tree/2} function.
%%
-%% <p>Note: The primary constructor functions for a node type should
-%% always have the same name as the node type itself.</p>
+%% Note: The primary constructor functions for a node type should
+%% always have the same name as the node type itself.
%%
%% @see tree/2
%% @see application/3
@@ -606,39 +600,38 @@ type(Node) ->
%% =====================================================================
-%% @spec is_leaf(Node::syntaxTree()) -> boolean()
-%%
-%% @doc Returns <code>true</code> if <code>Node</code> is a leaf node,
-%% otherwise <code>false</code>. The currently recognised leaf node
+%% @doc Returns `true' if `Node' is a leaf node,
+%% otherwise `false'. The currently recognised leaf node
%% types are:
-%% <p><center><table border="1">
+%%
+%% <center><table border="1">
%% <tr>
-%% <td><code>atom</code></td>
-%% <td><code>char</code></td>
-%% <td><code>comment</code></td>
-%% <td><code>eof_marker</code></td>
-%% <td><code>error_marker</code></td>
+%% <td>`atom'</td>
+%% <td>`char'</td>
+%% <td>`comment'</td>
+%% <td>`eof_marker'</td>
+%% <td>`error_marker'</td>
%% </tr><tr>
-%% <td><code>float</code></td>
-%% <td><code>integer</code></td>
-%% <td><code>nil</code></td>
-%% <td><code>operator</code></td>
-%% <td><code>string</code></td>
+%% <td>`float'</td>
+%% <td>`integer'</td>
+%% <td>`nil'</td>
+%% <td>`operator'</td>
+%% <td>`string'</td>
%% </tr><tr>
-%% <td><code>text</code></td>
-%% <td><code>underscore</code></td>
-%% <td><code>variable</code></td>
-%% <td><code>warning_marker</code></td>
+%% <td>`text'</td>
+%% <td>`underscore'</td>
+%% <td>`variable'</td>
+%% <td>`warning_marker'</td>
%% </tr>
-%% </table></center></p>
-%% <p>A node of type <code>tuple</code> is a leaf node if and only if
-%% its arity is zero.</p>
+%% </table></center>
%%
-%% <p>Note: not all literals are leaf nodes, and vice versa. E.g.,
+%% A node of type `tuple' is a leaf node if and only if its arity is zero.
+%%
+%% Note: not all literals are leaf nodes, and vice versa. E.g.,
%% tuples with nonzero arity and nonempty lists may be literals, but are
%% not leaf nodes. Variables, on the other hand, are leaf nodes but not
-%% literals.</p>
-%%
+%% literals.
+%%
%% @see type/1
%% @see is_literal/1
@@ -666,29 +659,29 @@ is_leaf(Node) ->
%% =====================================================================
-%% @spec is_form(Node::syntaxTree()) -> boolean()
-%%
-%% @doc Returns <code>true</code> if <code>Node</code> is a syntax tree
+%% @doc Returns `true' if `Node' is a syntax tree
%% representing a so-called "source code form", otherwise
-%% <code>false</code>. Forms are the Erlang source code units which,
+%% `false'. Forms are the Erlang source code units which,
%% placed in sequence, constitute an Erlang program. Current form types
%% are:
-%% <p><center><table border="1">
+%%
+%% <center><table border="1">
%% <tr>
-%% <td><code>attribute</code></td>
-%% <td><code>comment</code></td>
-%% <td><code>error_marker</code></td>
-%% <td><code>eof_marker</code></td>
-%% <td><code>form_list</code></td>
+%% <td>`attribute'</td>
+%% <td>`comment'</td>
+%% <td>`error_marker'</td>
+%% <td>`eof_marker'</td>
+%% <td>`form_list'</td>
%% </tr><tr>
-%% <td><code>function</code></td>
-%% <td><code>rule</code></td>
-%% <td><code>warning_marker</code></td>
-%% <td><code>text</code></td>
+%% <td>`function'</td>
+%% <td>`rule'</td>
+%% <td>`warning_marker'</td>
+%% <td>`text'</td>
%% </tr>
-%% </table></center></p>
+%% </table></center>
+%%
%% @see type/1
-%% @see attribute/2
+%% @see attribute/2
%% @see comment/2
%% @see eof_marker/0
%% @see error_marker/1
@@ -715,10 +708,8 @@ is_form(Node) ->
%% =====================================================================
-%% @spec get_pos(Node::syntaxTree()) -> term()
-%%
%% @doc Returns the position information associated with
-%% <code>Node</code>. This is usually a nonnegative integer (indicating
+%% `Node'. This is usually a nonnegative integer (indicating
%% the source code line number), but may be any term. By default, all
%% new tree nodes have their associated position information set to the
%% integer zero.
@@ -750,10 +741,7 @@ get_pos(Node) ->
%% =====================================================================
-%% @spec set_pos(Node::syntaxTree(), Pos::term()) -> syntaxTree()
-%%
-%% @doc Sets the position information of <code>Node</code> to
-%% <code>Pos</code>.
+%% @doc Sets the position information of `Node' to `Pos'.
%%
%% @see get_pos/1
%% @see copy_pos/2
@@ -774,14 +762,10 @@ set_pos(Node, Pos) ->
%% =====================================================================
-%% @spec copy_pos(Source::syntaxTree(), Target::syntaxTree()) ->
-%% syntaxTree()
-%%
-%% @doc Copies the position information from <code>Source</code> to
-%% <code>Target</code>.
+%% @doc Copies the position information from `Source' to `Target'.
%%
-%% <p>This is equivalent to <code>set_pos(Target,
-%% get_pos(Source))</code>, but potentially more efficient.</p>
+%% This is equivalent to `set_pos(Target,
+%% get_pos(Source))', but potentially more efficient.
%%
%% @see get_pos/1
%% @see set_pos/2
@@ -811,24 +795,20 @@ set_com(Node, Com) ->
%% =====================================================================
-%% @spec get_precomments(syntaxTree()) -> [syntaxTree()]
-%%
%% @doc Returns the associated pre-comments of a node. This is a
%% possibly empty list of abstract comments, in top-down textual order.
%% When the code is formatted, pre-comments are typically displayed
%% directly above the node. For example:
-%% <pre>
-%% % Pre-comment of function
-%% foo(X) -> {bar, X}.</pre>
+%% ```% Pre-comment of function
+%% foo(X) -> {bar, X}.'''
%%
-%% <p>If possible, the comment should be moved before any preceding
+%% If possible, the comment should be moved before any preceding
%% separator characters on the same line. E.g.:
-%% <pre>
-%% foo([X | Xs]) ->
-%% % Pre-comment of 'bar(X)' node
-%% [bar(X) | foo(Xs)];
-%% ...</pre>
-%% (where the comment is moved before the "<code>[</code>").</p>
+%% ```foo([X | Xs]) ->
+%% % Pre-comment of 'bar(X)' node
+%% [bar(X) | foo(Xs)];
+%% ...'''
+%% (where the comment is moved before the "`['").
%%
%% @see comment/2
%% @see set_precomments/2
@@ -846,11 +826,8 @@ get_precomments_1(#attr{com = #com{pre = Cs}}) -> Cs.
%% =====================================================================
-%% @spec set_precomments(Node::syntaxTree(),
-%% Comments::[syntaxTree()]) -> syntaxTree()
-%%
-%% @doc Sets the pre-comments of <code>Node</code> to
-%% <code>Comments</code>. <code>Comments</code> should be a possibly
+%% @doc Sets the pre-comments of `Node' to
+%% `Comments'. `Comments' should be a possibly
%% empty list of abstract comments, in top-down textual order.
%%
%% @see comment/2
@@ -880,15 +857,11 @@ set_precomments_1(#attr{com = Com} = Attr, Cs) ->
%% =====================================================================
-%% @spec add_precomments(Comments::[syntaxTree()],
-%% Node::syntaxTree()) -> syntaxTree()
+%% @doc Appends `Comments' to the pre-comments of `Node'.
%%
-%% @doc Appends <code>Comments</code> to the pre-comments of
-%% <code>Node</code>.
-%%
-%% <p>Note: This is equivalent to <code>set_precomments(Node,
-%% get_precomments(Node) ++ Comments)</code>, but potentially more
-%% efficient.</p>
+%% Note: This is equivalent to `set_precomments(Node,
+%% get_precomments(Node) ++ Comments)', but potentially more
+%% efficient.
%%
%% @see comment/2
%% @see get_precomments/1
@@ -915,24 +888,20 @@ add_precomments_1(Cs, #attr{com = Com} = Attr) ->
%% =====================================================================
-%% @spec get_postcomments(syntaxTree()) -> [syntaxTree()]
-%%
%% @doc Returns the associated post-comments of a node. This is a
%% possibly empty list of abstract comments, in top-down textual order.
%% When the code is formatted, post-comments are typically displayed to
%% the right of and/or below the node. For example:
-%% <pre>
-%% {foo, X, Y} % Post-comment of tuple</pre>
+%% ```{foo, X, Y} % Post-comment of tuple'''
%%
-%% <p>If possible, the comment should be moved past any following
+%% If possible, the comment should be moved past any following
%% separator characters on the same line, rather than placing the
%% separators on the following line. E.g.:
-%% <pre>
-%% foo([X | Xs], Y) ->
-%% foo(Xs, bar(X)); % Post-comment of 'bar(X)' node
-%% ...</pre>
-%% (where the comment is moved past the rightmost "<code>)</code>" and
-%% the "<code>;</code>").</p>
+%% ```foo([X | Xs], Y) ->
+%% foo(Xs, bar(X)); % Post-comment of 'bar(X)' node
+%% ...'''
+%% (where the comment is moved past the rightmost "`)'" and
+%% the "`;'").
%%
%% @see comment/2
%% @see set_postcomments/2
@@ -950,11 +919,8 @@ get_postcomments_1(#attr{com = #com{post = Cs}}) -> Cs.
%% =====================================================================
-%% @spec set_postcomments(Node::syntaxTree(),
-%% Comments::[syntaxTree()]) -> syntaxTree()
-%%
-%% @doc Sets the post-comments of <code>Node</code> to
-%% <code>Comments</code>. <code>Comments</code> should be a possibly
+%% @doc Sets the post-comments of `Node' to
+%% `Comments'. `Comments' should be a possibly
%% empty list of abstract comments, in top-down textual order
%%
%% @see comment/2
@@ -984,15 +950,11 @@ set_postcomments_1(#attr{com = Com} = Attr, Cs) ->
%% =====================================================================
-%% @spec add_postcomments(Comments::[syntaxTree()],
-%% Node::syntaxTree()) -> syntaxTree()
-%%
-%% @doc Appends <code>Comments</code> to the post-comments of
-%% <code>Node</code>.
+%% @doc Appends `Comments' to the post-comments of `Node'.
%%
-%% <p>Note: This is equivalent to <code>set_postcomments(Node,
-%% get_postcomments(Node) ++ Comments)</code>, but potentially more
-%% efficient.</p>
+%% Note: This is equivalent to `set_postcomments(Node,
+%% get_postcomments(Node) ++ Comments)', but potentially more
+%% efficient.
%%
%% @see comment/2
%% @see get_postcomments/1
@@ -1019,14 +981,12 @@ add_postcomments_1(Cs, #attr{com = Com} = Attr) ->
%% =====================================================================
-%% @spec has_comments(Node::syntaxTree()) -> boolean()
+%% @doc Yields `false' if the node has no associated
+%% comments, and `true' otherwise.
%%
-%% @doc Yields <code>false</code> if the node has no associated
-%% comments, and <code>true</code> otherwise.
-%%
-%% <p>Note: This is equivalent to <code>(get_precomments(Node) == [])
-%% and (get_postcomments(Node) == [])</code>, but potentially more
-%% efficient.</p>
+%% Note: This is equivalent to `(get_precomments(Node) == [])
+%% and (get_postcomments(Node) == [])', but potentially more
+%% efficient.
%%
%% @see get_precomments/1
%% @see get_postcomments/1
@@ -1050,13 +1010,11 @@ has_comments(_) -> false.
%% =====================================================================
-%% @spec remove_comments(Node::syntaxTree()) -> syntaxTree()
-%%
-%% @doc Clears the associated comments of <code>Node</code>.
+%% @doc Clears the associated comments of `Node'.
%%
-%% <p>Note: This is equivalent to
-%% <code>set_precomments(set_postcomments(Node, []), [])</code>, but
-%% potentially more efficient.</p>
+%% Note: This is equivalent to
+%% `set_precomments(set_postcomments(Node, []), [])', but
+%% potentially more efficient.
%%
%% @see set_precomments/2
%% @see set_postcomments/2
@@ -1075,16 +1033,12 @@ remove_comments(Node) ->
%% =====================================================================
-%% @spec copy_comments(Source::syntaxTree(), Target::syntaxTree()) ->
-%% syntaxTree()
-%%
-%% @doc Copies the pre- and postcomments from <code>Source</code> to
-%% <code>Target</code>.
+%% @doc Copies the pre- and postcomments from `Source' to `Target'.
%%
-%% <p>Note: This is equivalent to
-%% <code>set_postcomments(set_precomments(Target,
-%% get_precomments(Source)), get_postcomments(Source))</code>, but
-%% potentially more efficient.</p>
+%% Note: This is equivalent to
+%% `set_postcomments(set_precomments(Target,
+%% get_precomments(Source)), get_postcomments(Source))', but
+%% potentially more efficient.
%%
%% @see comment/2
%% @see get_precomments/1
@@ -1099,16 +1053,13 @@ copy_comments(Source, Target) ->
%% =====================================================================
-%% @spec join_comments(Source::syntaxTree(), Target::syntaxTree()) ->
-%% syntaxTree()
+%% @doc Appends the comments of `Source' to the current
+%% comments of `Target'.
%%
-%% @doc Appends the comments of <code>Source</code> to the current
-%% comments of <code>Target</code>.
-%%
-%% <p>Note: This is equivalent to
-%% <code>add_postcomments(get_postcomments(Source),
-%% add_precomments(get_precomments(Source), Target))</code>, but
-%% potentially more efficient.</p>
+%% Note: This is equivalent to
+%% `add_postcomments(get_postcomments(Source),
+%% add_precomments(get_precomments(Source), Target))', but
+%% potentially more efficient.
%%
%% @see comment/2
%% @see get_precomments/1
@@ -1125,8 +1076,6 @@ join_comments(Source, Target) ->
%% =====================================================================
-%% @spec get_ann(syntaxTree()) -> [term()]
-%%
%% @doc Returns the list of user annotations associated with a syntax
%% tree node. For a newly created node, this is the empty list. The
%% annotations may be any terms.
@@ -1142,11 +1091,7 @@ get_ann(_) -> [].
%% =====================================================================
-%% @spec set_ann(Node::syntaxTree(), Annotations::[term()]) ->
-%% syntaxTree()
-%%
-%% @doc Sets the list of user annotations of <code>Node</code> to
-%% <code>Annotations</code>.
+%% @doc Sets the list of user annotations of `Node' to `Annotations'.
%%
%% @see get_ann/1
%% @see add_ann/2
@@ -1168,13 +1113,11 @@ set_ann(Node, As) ->
%% =====================================================================
-%% @spec add_ann(Annotation::term(), Node::syntaxTree()) -> syntaxTree()
-%%
-%% @doc Appends the term <code>Annotation</code> to the list of user
-%% annotations of <code>Node</code>.
+%% @doc Appends the term `Annotation' to the list of user
+%% annotations of `Node'.
%%
-%% <p>Note: this is equivalent to <code>set_ann(Node, [Annotation |
-%% get_ann(Node)])</code>, but potentially more efficient.</p>
+%% Note: this is equivalent to `set_ann(Node, [Annotation |
+%% get_ann(Node)])', but potentially more efficient.
%%
%% @see get_ann/1
%% @see set_ann/2
@@ -1195,14 +1138,10 @@ add_ann(A, Node) ->
%% =====================================================================
-%% @spec copy_ann(Source::syntaxTree(), Target::syntaxTree()) ->
-%% syntaxTree()
+%% @doc Copies the list of user annotations from `Source' to `Target'.
%%
-%% @doc Copies the list of user annotations from <code>Source</code> to
-%% <code>Target</code>.
-%%
-%% <p>Note: this is equivalent to <code>set_ann(Target,
-%% get_ann(Source))</code>, but potentially more efficient.</p>
+%% Note: this is equivalent to `set_ann(Target,
+%% get_ann(Source))', but potentially more efficient.
%%
%% @see get_ann/1
%% @see set_ann/2
@@ -1214,23 +1153,20 @@ copy_ann(Source, Target) ->
%% =====================================================================
-%% @spec get_attrs(syntaxTree()) -> syntaxTreeAttributes()
-%%
%% @doc Returns a representation of the attributes associated with a
%% syntax tree node. The attributes are all the extra information that
%% can be attached to a node. Currently, this includes position
%% information, source code comments, and user annotations. The result
%% of this function cannot be inspected directly; only attached to
-%% another node (cf. <code>set_attrs/2</code>).
+%% another node (see {@link set_attrs/2}).
%%
-%% <p>For accessing individual attributes, see <code>get_pos/1</code>,
-%% <code>get_ann/1</code>, <code>get_precomments/1</code> and
-%% <code>get_postcomments/1</code>.</p>
+%% For accessing individual attributes, see {@link get_pos/1},
+%% {@link get_ann/1}, {@link get_precomments/1} and
+%% {@link get_postcomments/1}.
%%
%% @type syntaxTreeAttributes(). This is an abstract representation of
-%% syntax tree node attributes; see the function <a
-%% href="#get_attrs-1"><code>get_attrs/1</code></a>.
-%%
+%% syntax tree node attributes; see the function {@link get_attrs/1}.
+%%
%% @see set_attrs/2
%% @see get_pos/1
%% @see get_ann/1
@@ -1247,11 +1183,7 @@ get_attrs(Node) -> #attr{pos = get_pos(Node),
%% =====================================================================
-%% @spec set_attrs(Node::syntaxTree(),
-%% Attributes::syntaxTreeAttributes()) -> syntaxTree()
-%%
-%% @doc Sets the attributes of <code>Node</code> to
-%% <code>Attributes</code>.
+%% @doc Sets the attributes of `Node' to `Attributes'.
%%
%% @see get_attrs/1
%% @see copy_attrs/2
@@ -1270,14 +1202,10 @@ set_attrs(Node, Attr) ->
%% =====================================================================
-%% @spec copy_attrs(Source::syntaxTree(), Target::syntaxTree()) ->
-%% syntaxTree()
+%% @doc Copies the attributes from `Source' to `Target'.
%%
-%% @doc Copies the attributes from <code>Source</code> to
-%% <code>Target</code>.
-%%
-%% <p>Note: this is equivalent to <code>set_attrs(Target,
-%% get_attrs(Source))</code>, but potentially more efficient.</p>
+%% Note: this is equivalent to `set_attrs(Target,
+%% get_attrs(Source))', but potentially more efficient.
%%
%% @see get_attrs/1
%% @see set_attrs/2
@@ -1289,7 +1217,6 @@ copy_attrs(S, T) ->
%% =====================================================================
-%% @spec comment(Strings) -> syntaxTree()
%% @equiv comment(none, Strings)
-spec comment([string()]) -> syntaxTree().
@@ -1299,22 +1226,19 @@ comment(Strings) ->
%% =====================================================================
-%% @spec comment(Padding, Strings::[string()]) -> syntaxTree()
-%% Padding = none | integer()
-%%
%% @doc Creates an abstract comment with the given padding and text. If
-%% <code>Strings</code> is a (possibly empty) list
+%% `Strings' is a (possibly empty) list
%% <code>["<em>Txt1</em>", ..., "<em>TxtN</em>"]</code>, the result
%% represents the source code text
%% <pre>
-%% %<em>Txt1</em>
-%% ...
-%% %<em>TxtN</em></pre>
-%% <code>Padding</code> states the number of empty character positions
+%% %<em>Txt1</em>
+%% ...
+%% %<em>TxtN</em></pre>
+%% `Padding' states the number of empty character positions
%% to the left of the comment separating it horizontally from
-%% source code on the same line (if any). If <code>Padding</code> is
-%% <code>none</code>, a default positive number is used. If
-%% <code>Padding</code> is an integer less than 1, there should be no
+%% source code on the same line (if any). If `Padding' is
+%% `none', a default positive number is used. If
+%% `Padding' is an integer less than 1, there should be no
%% separating space. Comments are in themselves regarded as source
%% program forms.
%%
@@ -1338,8 +1262,6 @@ comment(Pad, Strings) ->
%% =====================================================================
-%% @spec comment_text(syntaxTree()) -> [string()]
-%%
%% @doc Returns the lines of text of the abstract comment.
%%
%% @see comment/2
@@ -1351,11 +1273,8 @@ comment_text(Node) ->
%% =====================================================================
-%% @spec comment_padding(syntaxTree()) -> none | integer()
-%%
%% @doc Returns the amount of padding before the comment, or
-%% <code>none</code>. The latter means that a default padding may be
-%% used.
+%% `none'. The latter means that a default padding may be used.
%%
%% @see comment/2
@@ -1366,23 +1285,21 @@ comment_padding(Node) ->
%% =====================================================================
-%% @spec form_list(Forms::[syntaxTree()]) -> syntaxTree()
-%%
%% @doc Creates an abstract sequence of "source code forms". If
-%% <code>Forms</code> is <code>[F1, ..., Fn]</code>, where each
-%% <code>Fi</code> is a form (cf. <code>is_form/1</code>, the result
+%% `Forms' is `[F1, ..., Fn]', where each
+%% `Fi' is a form (see {@link is_form/1}, the result
%% represents
%% <pre>
-%% <em>F1</em>
-%% ...
-%% <em>Fn</em></pre>
-%% where the <code>Fi</code> are separated by one or more line breaks. A
-%% node of type <code>form_list</code> is itself regarded as a source
-%% code form; cf. <code>flatten_form_list/1</code>.
-%%
-%% <p>Note: this is simply a way of grouping source code forms as a
+%% <em>F1</em>
+%% ...
+%% <em>Fn</em></pre>
+%% where the `Fi' are separated by one or more line breaks. A
+%% node of type `form_list' is itself regarded as a source
+%% code form; see {@link flatten_form_list/1}.
+%%
+%% Note: this is simply a way of grouping source code forms as a
%% single syntax tree, usually in order to form an Erlang module
-%% definition.</p>
+%% definition.
%%
%% @see form_list_elements/1
%% @see is_form/1
@@ -1401,9 +1318,7 @@ form_list(Forms) ->
%% =====================================================================
-%% @spec form_list_elements(syntaxTree()) -> [syntaxTree()]
-%%
-%% @doc Returns the list of subnodes of a <code>form_list</code> node.
+%% @doc Returns the list of subnodes of a `form_list' node.
%%
%% @see form_list/1
@@ -1414,10 +1329,8 @@ form_list_elements(Node) ->
%% =====================================================================
-%% @spec flatten_form_list(Node::syntaxTree()) -> syntaxTree()
-%%
-%% @doc Flattens sublists of a <code>form_list</code> node. Returns
-%% <code>Node</code> with all subtrees of type <code>form_list</code>
+%% @doc Flattens sublists of a `form_list' node. Returns
+%% `Node' with all subtrees of type `form_list'
%% recursively expanded, yielding a single "flat" abstract form
%% sequence.
%%
@@ -1443,10 +1356,8 @@ flatten_form_list_1([], As) ->
%% =====================================================================
-%% @spec text(String::string()) -> syntaxTree()
-%%
%% @doc Creates an abstract piece of source code text. The result
-%% represents exactly the sequence of characters in <code>String</code>.
+%% represents exactly the sequence of characters in `String'.
%% This is useful in cases when one wants full control of the resulting
%% output, e.g., for the appearance of floating-point numbers or macro
%% definitions.
@@ -1463,10 +1374,7 @@ text(String) ->
%% =====================================================================
-%% @spec text_string(syntaxTree()) -> string()
-%%
-%% @doc Returns the character sequence represented by a
-%% <code>text</code> node.
+%% @doc Returns the character sequence represented by a `text' node.
%%
%% @see text/1
@@ -1477,18 +1385,15 @@ text_string(Node) ->
%% =====================================================================
-%% @spec variable(Name) -> syntaxTree()
-%% Name = atom() | string()
-%%
%% @doc Creates an abstract variable with the given name.
-%% <code>Name</code> may be any atom or string that represents a
+%% `Name' may be any atom or string that represents a
%% lexically valid variable name, but <em>not</em> a single underscore
-%% character; cf. <code>underscore/0</code>.
+%% character; see {@link underscore/0}.
%%
-%% <p>Note: no checking is done whether the character sequence
+%% Note: no checking is done whether the character sequence
%% represents a proper variable name, i.e., whether or not its first
%% character is an uppercase Erlang character, or whether it does not
-%% contain control characters, whitespace, etc.</p>
+%% contain control characters, whitespace, etc.
%%
%% @see variable_name/1
%% @see variable_literal/1
@@ -1517,9 +1422,7 @@ revert_variable(Node) ->
%% =====================================================================
-%% @spec variable_name(syntaxTree()) -> atom()
-%%
-%% @doc Returns the name of a <code>variable</code> node as an atom.
+%% @doc Returns the name of a `variable' node as an atom.
%%
%% @see variable/1
@@ -1535,9 +1438,7 @@ variable_name(Node) ->
%% =====================================================================
-%% @spec variable_literal(syntaxTree()) -> string()
-%%
-%% @doc Returns the name of a <code>variable</code> node as a string.
+%% @doc Returns the name of a `variable' node as a string.
%%
%% @see variable/1
@@ -1553,9 +1454,7 @@ variable_literal(Node) ->
%% =====================================================================
-%% @spec underscore() -> syntaxTree()
-%%
-%% @doc Creates an abstract universal pattern ("<code>_</code>"). The
+%% @doc Creates an abstract universal pattern ("`_'"). The
%% lexical representation is a single underscore character. Note that
%% this is <em>not</em> a variable, lexically speaking.
%%
@@ -1579,10 +1478,8 @@ revert_underscore(Node) ->
%% =====================================================================
-%% @spec integer(Value::integer()) -> syntaxTree()
-%%
%% @doc Creates an abstract integer literal. The lexical representation
-%% is the canonical decimal numeral of <code>Value</code>.
+%% is the canonical decimal numeral of `Value'.
%%
%% @see integer_value/1
%% @see integer_literal/1
@@ -1608,11 +1505,8 @@ revert_integer(Node) ->
%% =====================================================================
-%% @spec is_integer(Node::syntaxTree(), Value::integer()) -> boolean()
-%%
-%% @doc Returns <code>true</code> if <code>Node</code> has type
-%% <code>integer</code> and represents <code>Value</code>, otherwise
-%% <code>false</code>.
+%% @doc Returns `true' if `Node' has type
+%% `integer' and represents `Value', otherwise `false'.
%%
%% @see integer/1
@@ -1630,9 +1524,7 @@ is_integer(Node, Value) ->
%% =====================================================================
-%% @spec integer_value(syntaxTree()) -> integer()
-%%
-%% @doc Returns the value represented by an <code>integer</code> node.
+%% @doc Returns the value represented by an `integer' node.
%%
%% @see integer/1
@@ -1648,10 +1540,7 @@ integer_value(Node) ->
%% =====================================================================
-%% @spec integer_literal(syntaxTree()) -> string()
-%%
-%% @doc Returns the numeral string represented by an
-%% <code>integer</code> node.
+%% @doc Returns the numeral string represented by an `integer' node.
%%
%% @see integer/1
@@ -1662,11 +1551,8 @@ integer_literal(Node) ->
%% =====================================================================
-%% @spec float(Value::float()) -> syntaxTree()
-%%
%% @doc Creates an abstract floating-point literal. The lexical
-%% representation is the decimal floating-point numeral of
-%% <code>Value</code>.
+%% representation is the decimal floating-point numeral of `Value'.
%%
%% @see float_value/1
%% @see float_literal/1
@@ -1701,9 +1587,7 @@ revert_float(Node) ->
%% =====================================================================
-%% @spec float_value(syntaxTree()) -> float()
-%%
-%% @doc Returns the value represented by a <code>float</code> node. Note
+%% @doc Returns the value represented by a `float' node. Note
%% that floating-point values should usually not be compared for
%% equality.
%%
@@ -1721,10 +1605,7 @@ float_value(Node) ->
%% =====================================================================
-%% @spec float_literal(syntaxTree()) -> string()
-%%
-%% @doc Returns the numeral string represented by a <code>float</code>
-%% node.
+%% @doc Returns the numeral string represented by a `float' node.
%%
%% @see float/1
@@ -1735,17 +1616,15 @@ float_literal(Node) ->
%% =====================================================================
-%% @spec char(Value::char()) -> syntaxTree()
-%%
%% @doc Creates an abstract character literal. The result represents
-%% "<code>$<em>Name</em></code>", where <code>Name</code> corresponds to
-%% <code>Value</code>.
+%% "<code>$<em>Name</em></code>", where `Name' corresponds to
+%% `Value'.
%%
-%% <p>Note: the literal corresponding to a particular character value is
-%% not uniquely defined. E.g., the character "<code>a</code>" can be
-%% written both as "<code>$a</code>" and "<code>$\141</code>", and a Tab
-%% character can be written as "<code>$\11</code>", "<code>$\011</code>"
-%% or "<code>$\t</code>".</p>
+%% Note: the literal corresponding to a particular character value is
+%% not uniquely defined. E.g., the character "`a'" can be
+%% written both as "`$a'" and "`$\141'", and a Tab
+%% character can be written as "`$\11'", "`$\011'"
+%% or "`$\t'".
%%
%% @see char_value/1
%% @see char_literal/1
@@ -1771,11 +1650,8 @@ revert_char(Node) ->
%% =====================================================================
-%% @spec is_char(Node::syntaxTree(), Value::char()) -> boolean()
-%%
-%% @doc Returns <code>true</code> if <code>Node</code> has type
-%% <code>char</code> and represents <code>Value</code>, otherwise
-%% <code>false</code>.
+%% @doc Returns `true' if `Node' has type
+%% `char' and represents `Value', otherwise `false'.
%%
%% @see char/1
@@ -1793,9 +1669,7 @@ is_char(Node, Value) ->
%% =====================================================================
-%% @spec char_value(syntaxTree()) -> char()
-%%
-%% @doc Returns the value represented by a <code>char</code> node.
+%% @doc Returns the value represented by a `char' node.
%%
%% @see char/1
@@ -1811,10 +1685,8 @@ char_value(Node) ->
%% =====================================================================
-%% @spec char_literal(syntaxTree()) -> string()
-%%
-%% @doc Returns the literal string represented by a <code>char</code>
-%% node. This includes the leading "<code>$</code>" character.
+%% @doc Returns the literal string represented by a `char'
+%% node. This includes the leading "`$'" character.
%%
%% @see char/1
@@ -1825,16 +1697,14 @@ char_literal(Node) ->
%% =====================================================================
-%% @spec string(Value::string()) -> syntaxTree()
-%%
%% @doc Creates an abstract string literal. The result represents
%% <code>"<em>Text</em>"</code> (including the surrounding
-%% double-quotes), where <code>Text</code> corresponds to the sequence
-%% of characters in <code>Value</code>, but not representing a
-%% <em>specific</em> string literal. E.g., the result of
-%% <code>string("x\ny")</code> represents any and all of
-%% <code>"x\ny"</code>, <code>"x\12y"</code>, <code>"x\012y"</code> and
-%% <code>"x\^Jy"</code>; cf. <code>char/1</code>.
+%% double-quotes), where `Text' corresponds to the sequence
+%% of characters in `Value', but not representing a
+%% <em>specific</em> string literal.
+%%
+%% For example, the result of `string("x\ny")' represents any and all of
+%% `"x\ny"', `"x\12y"', `"x\012y"' and `"x\^Jy"'; see {@link char/1}.
%%
%% @see string_value/1
%% @see string_literal/1
@@ -1861,11 +1731,8 @@ revert_string(Node) ->
%% =====================================================================
-%% @spec is_string(Node::syntaxTree(), Value::string()) -> boolean()
-%%
-%% @doc Returns <code>true</code> if <code>Node</code> has type
-%% <code>string</code> and represents <code>Value</code>, otherwise
-%% <code>false</code>.
+%% @doc Returns `true' if `Node' has type
+%% `string' and represents `Value', otherwise `false'.
%%
%% @see string/1
@@ -1883,9 +1750,7 @@ is_string(Node, Value) ->
%% =====================================================================
-%% @spec string_value(syntaxTree()) -> string()
-%%
-%% @doc Returns the value represented by a <code>string</code> node.
+%% @doc Returns the value represented by a `string' node.
%%
%% @see string/1
@@ -1901,9 +1766,7 @@ string_value(Node) ->
%% =====================================================================
-%% @spec string_literal(syntaxTree()) -> string()
-%%
-%% @doc Returns the literal string represented by a <code>string</code>
+%% @doc Returns the literal string represented by a `string'
%% node. This includes surrounding double-quote characters.
%%
%% @see string/1
@@ -1915,11 +1778,8 @@ string_literal(Node) ->
%% =====================================================================
-%% @spec atom(Name) -> syntaxTree()
-%% Name = atom() | string()
-%%
%% @doc Creates an abstract atom literal. The print name of the atom is
-%% the character sequence represented by <code>Name</code>.
+%% the character sequence represented by `Name'.
%%
%% @see atom_value/1
%% @see atom_name/1
@@ -1948,11 +1808,8 @@ revert_atom(Node) ->
%% =====================================================================
-%% @spec is_atom(Node::syntaxTree(), Value::atom()) -> boolean()
-%%
-%% @doc Returns <code>true</code> if <code>Node</code> has type
-%% <code>atom</code> and represents <code>Value</code>, otherwise
-%% <code>false</code>.
+%% @doc Returns `true' if `Node' has type
+%% `atom' and represents `Value', otherwise `false'.
%%
%% @see atom/1
@@ -1970,9 +1827,7 @@ is_atom(Node, Value) ->
%% =====================================================================
-%% @spec atom_value(syntaxTree()) -> atom()
-%%
-%% @doc Returns the value represented by an <code>atom</code> node.
+%% @doc Returns the value represented by an `atom' node.
%%
%% @see atom/1
@@ -1988,9 +1843,7 @@ atom_value(Node) ->
%% =====================================================================
-%% @spec atom_name(syntaxTree()) -> string()
-%%
-%% @doc Returns the printname of an <code>atom</code> node.
+%% @doc Returns the printname of an `atom' node.
%%
%% @see atom/1
@@ -2001,15 +1854,12 @@ atom_name(Node) ->
%% =====================================================================
-%% @spec atom_literal(syntaxTree()) -> string()
-%%
-%% @doc Returns the literal string represented by an <code>atom</code>
+%% @doc Returns the literal string represented by an `atom'
%% node. This includes surrounding single-quote characters if necessary.
%%
-%% <p>Note that e.g. the result of <code>atom("x\ny")</code> represents
-%% any and all of <code>'x\ny'</code>, <code>'x\12y'</code>,
-%% <code>'x\012y'</code> and <code>'x\^Jy\'</code>; cf.
-%% <code>string/1</code>.</p>
+%% Note that e.g. the result of `atom("x\ny")' represents
+%% any and all of `'x\ny'', `'x\12y'',
+%% `'x\012y'' and `'x\^Jy\''; see {@link string/1}.
%%
%% @see atom/1
%% @see string/1
@@ -2021,14 +1871,12 @@ atom_literal(Node) ->
%% =====================================================================
-%% @spec tuple(Elements::[syntaxTree()]) -> syntaxTree()
-%%
-%% @doc Creates an abstract tuple. If <code>Elements</code> is
-%% <code>[X1, ..., Xn]</code>, the result represents
+%% @doc Creates an abstract tuple. If `Elements' is
+%% `[X1, ..., Xn]', the result represents
%% "<code>{<em>X1</em>, ..., <em>Xn</em>}</code>".
%%
-%% <p>Note: The Erlang language has distinct 1-tuples, i.e.,
-%% <code>{X}</code> is always distinct from <code>X</code> itself.</p>
+%% Note: The Erlang language has distinct 1-tuples, i.e.,
+%% `{X}' is always distinct from `X' itself.
%%
%% @see tuple_elements/1
%% @see tuple_size/1
@@ -2055,10 +1903,7 @@ revert_tuple(Node) ->
%% =====================================================================
-%% @spec tuple_elements(syntaxTree()) -> [syntaxTree()]
-%%
-%% @doc Returns the list of element subtrees of a <code>tuple</code>
-%% node.
+%% @doc Returns the list of element subtrees of a `tuple' node.
%%
%% @see tuple/1
@@ -2074,13 +1919,11 @@ tuple_elements(Node) ->
%% =====================================================================
-%% @spec tuple_size(syntaxTree()) -> integer()
-%%
-%% @doc Returns the number of elements of a <code>tuple</code> node.
+%% @doc Returns the number of elements of a `tuple' node.
%%
-%% <p>Note: this is equivalent to
-%% <code>length(tuple_elements(Node))</code>, but potentially more
-%% efficient.</p>
+%% Note: this is equivalent to
+%% `length(tuple_elements(Node))', but potentially more
+%% efficient.
%%
%% @see tuple/1
%% @see tuple_elements/1
@@ -2092,7 +1935,6 @@ tuple_size(Node) ->
%% =====================================================================
-%% @spec list(List) -> syntaxTree()
%% @equiv list(List, none)
-spec list([syntaxTree()]) -> syntaxTree().
@@ -2102,35 +1944,31 @@ list(List) ->
%% =====================================================================
-%% @spec list(List, Tail) -> syntaxTree()
-%% List = [syntaxTree()]
-%% Tail = none | syntaxTree()
-%%
%% @doc Constructs an abstract list skeleton. The result has type
-%% <code>list</code> or <code>nil</code>. If <code>List</code> is a
-%% nonempty list <code>[E1, ..., En]</code>, the result has type
-%% <code>list</code> and represents either "<code>[<em>E1</em>, ...,
-%% <em>En</em>]</code>", if <code>Tail</code> is <code>none</code>, or
+%% `list' or `nil'. If `List' is a
+%% nonempty list `[E1, ..., En]', the result has type
+%% `list' and represents either "<code>[<em>E1</em>, ...,
+%% <em>En</em>]</code>", if `Tail' is `none', or
%% otherwise "<code>[<em>E1</em>, ..., <em>En</em> |
-%% <em>Tail</em>]</code>". If <code>List</code> is the empty list,
-%% <code>Tail</code> <em>must</em> be <code>none</code>, and in that
-%% case the result has type <code>nil</code> and represents
-%% "<code>[]</code>" (cf. <code>nil/0</code>).
+%% <em>Tail</em>]</code>". If `List' is the empty list,
+%% `Tail' <em>must</em> be `none', and in that
+%% case the result has type `nil' and represents
+%% "`[]'" (see {@link nil/0}).
%%
-%% <p>The difference between lists as semantic objects (built up of
+%% The difference between lists as semantic objects (built up of
%% individual "cons" and "nil" terms) and the various syntactic forms
%% for denoting lists may be bewildering at first. This module provides
%% functions both for exact control of the syntactic representation as
%% well as for the simple composition and deconstruction in terms of
-%% cons and head/tail operations.</p>
+%% cons and head/tail operations.
%%
-%% <p>Note: in <code>list(Elements, none)</code>, the "nil" list
-%% terminator is implicit and has no associated information (cf.
-%% <code>get_attrs/1</code>), while in the seemingly equivalent
-%% <code>list(Elements, Tail)</code> when <code>Tail</code> has type
-%% <code>nil</code>, the list terminator subtree <code>Tail</code> may
+%% Note: in `list(Elements, none)', the "nil" list
+%% terminator is implicit and has no associated information (see
+%% {@link get_attrs/1}), while in the seemingly equivalent
+%% `list(Elements, Tail)' when `Tail' has type
+%% `nil', the list terminator subtree `Tail' may
%% have attached attributes such as position, comments, and annotations,
-%% which will be preserved in the result.</p>
+%% which will be preserved in the result.
%%
%% @see nil/0
%% @see list/1
@@ -2187,10 +2025,8 @@ revert_list(Node) ->
S, P).
%% =====================================================================
-%% @spec nil() -> syntaxTree()
-%%
%% @doc Creates an abstract empty list. The result represents
-%% "<code>[]</code>". The empty list is traditionally called "nil".
+%% "`[]'". The empty list is traditionally called "nil".
%%
%% @see list/2
%% @see is_list_skeleton/1
@@ -2213,13 +2049,11 @@ revert_nil(Node) ->
%% =====================================================================
-%% @spec list_prefix(Node::syntaxTree()) -> [syntaxTree()]
-%%
-%% @doc Returns the prefix element subtrees of a <code>list</code> node.
-%% If <code>Node</code> represents "<code>[<em>E1</em>, ...,
+%% @doc Returns the prefix element subtrees of a `list' node.
+%% If `Node' represents "<code>[<em>E1</em>, ...,
%% <em>En</em>]</code>" or "<code>[<em>E1</em>, ..., <em>En</em> |
-%% <em>Tail</em>]</code>", the returned value is <code>[E1, ...,
-%% En]</code>.
+%% <em>Tail</em>]</code>", the returned value is `[E1, ...,
+%% En]'.
%%
%% @see list/2
@@ -2227,28 +2061,31 @@ revert_nil(Node) ->
list_prefix(Node) ->
case unwrap(Node) of
- {cons, _, Head, _} ->
- [Head];
+ {cons, _, Head, Tail} ->
+ [Head | cons_prefix(Tail)];
Node1 ->
(data(Node1))#list.prefix
end.
+%% collects sequences of conses; cf. cons_suffix/1 below
+cons_prefix({cons, _, Head, Tail}) ->
+ [Head | cons_prefix(Tail)];
+cons_prefix(_) ->
+ [].
+
%% =====================================================================
-%% @spec list_suffix(Node::syntaxTree()) -> none | syntaxTree()
-%%
-%% @doc Returns the suffix subtree of a <code>list</code> node, if one
-%% exists. If <code>Node</code> represents "<code>[<em>E1</em>, ...,
+%% @doc Returns the suffix subtree of a `list' node, if one
+%% exists. If `Node' represents "<code>[<em>E1</em>, ...,
%% <em>En</em> | <em>Tail</em>]</code>", the returned value is
-%% <code>Tail</code>, otherwise, i.e., if <code>Node</code> represents
-%% "<code>[<em>E1</em>, ..., <em>En</em>]</code>", <code>none</code> is
+%% `Tail', otherwise, i.e., if `Node' represents
+%% "<code>[<em>E1</em>, ..., <em>En</em>]</code>", `none' is
%% returned.
%%
-%% <p>Note that even if this function returns some <code>Tail</code>
-%% that is not <code>none</code>, the type of <code>Tail</code> can be
-%% <code>nil</code>, if the tail has been given explicitly, and the list
-%% skeleton has not been compacted (cf.
-%% <code>compact_list/1</code>).</p>
+%% Note that even if this function returns some `Tail'
+%% that is not `none', the type of `Tail' can be
+%% `nil', if the tail has been given explicitly, and the list
+%% skeleton has not been compacted (see {@link compact_list/1}).
%%
%% @see list/2
%% @see nil/0
@@ -2259,34 +2096,36 @@ list_prefix(Node) ->
list_suffix(Node) ->
case unwrap(Node) of
{cons, _, _, Tail} ->
- %% If there could be comments/annotations on the tail node,
- %% we should not return `none' even if it has type `nil'.
- case Tail of
+ case cons_suffix(Tail) of
{nil, _} ->
- none; % no interesting information is lost
- _ ->
- Tail
+ none;
+ Tail1 ->
+ Tail1
end;
Node1 ->
(data(Node1))#list.suffix
end.
+%% skips sequences of conses; cf. cons_prefix/1 above
+cons_suffix({cons, _, _, Tail}) ->
+ cons_suffix(Tail);
+cons_suffix(Tail) ->
+ Tail.
+
%% =====================================================================
-%% @spec cons(Head::syntaxTree(), Tail::syntaxTree()) -> syntaxTree()
-%%
%% @doc "Optimising" list skeleton cons operation. Creates an abstract
-%% list skeleton whose first element is <code>Head</code> and whose tail
-%% corresponds to <code>Tail</code>. This is similar to
-%% <code>list([Head], Tail)</code>, except that <code>Tail</code> may
-%% not be <code>none</code>, and that the result does not necessarily
+%% list skeleton whose first element is `Head' and whose tail
+%% corresponds to `Tail'. This is similar to
+%% `list([Head], Tail)', except that `Tail' may
+%% not be `none', and that the result does not necessarily
%% represent exactly "<code>[<em>Head</em> | <em>Tail</em>]</code>", but
-%% may depend on the <code>Tail</code> subtree. E.g., if
-%% <code>Tail</code> represents <code>[X, Y]</code>, the result may
+%% may depend on the `Tail' subtree. E.g., if
+%% `Tail' represents `[X, Y]', the result may
%% represent "<code>[<em>Head</em>, X, Y]</code>", rather than
%% "<code>[<em>Head</em> | [X, Y]]</code>". Annotations on
-%% <code>Tail</code> itself may be lost if <code>Tail</code> represents
-%% a list skeleton, but comments on <code>Tail</code> are propagated to
+%% `Tail' itself may be lost if `Tail' represents
+%% a list skeleton, but comments on `Tail' are propagated to
%% the result.
%%
%% @see list/2
@@ -2308,10 +2147,8 @@ cons(Head, Tail) ->
%% =====================================================================
-%% @spec list_head(Node::syntaxTree()) -> syntaxTree()
-%%
-%% @doc Returns the head element subtree of a <code>list</code> node. If
-%% <code>Node</code> represents "<code>[<em>Head</em> ...]</code>", the
+%% @doc Returns the head element subtree of a `list' node. If
+%% `Node' represents "<code>[<em>Head</em> ...]</code>", the
%% result will represent "<code><em>Head</em></code>".
%%
%% @see list/2
@@ -2325,15 +2162,13 @@ list_head(Node) ->
%% =====================================================================
-%% @spec list_tail(Node::syntaxTree()) -> syntaxTree()
-%%
-%% @doc Returns the tail of a <code>list</code> node. If
-%% <code>Node</code> represents a single-element list
+%% @doc Returns the tail of a `list' node. If
+%% `Node' represents a single-element list
%% "<code>[<em>E</em>]</code>", then the result has type
-%% <code>nil</code>, representing "<code>[]</code>". If
-%% <code>Node</code> represents "<code>[<em>E1</em>, <em>E2</em>
+%% `nil', representing "`[]'". If
+%% `Node' represents "<code>[<em>E1</em>, <em>E2</em>
%% ...]</code>", the result will represent "<code>[<em>E2</em>
-%% ...]</code>", and if <code>Node</code> represents
+%% ...]</code>", and if `Node' represents
%% "<code>[<em>Head</em> | <em>Tail</em>]</code>", the result will
%% represent "<code><em>Tail</em></code>".
%%
@@ -2358,10 +2193,8 @@ list_tail(Node) ->
%% =====================================================================
-%% @spec is_list_skeleton(syntaxTree()) -> boolean()
-%%
-%% @doc Returns <code>true</code> if <code>Node</code> has type
-%% <code>list</code> or <code>nil</code>, otherwise <code>false</code>.
+%% @doc Returns `true' if `Node' has type
+%% `list' or `nil', otherwise `false'.
%%
%% @see list/2
%% @see nil/0
@@ -2377,24 +2210,22 @@ is_list_skeleton(Node) ->
%% =====================================================================
-%% @spec is_proper_list(Node::syntaxTree()) -> boolean()
-%%
-%% @doc Returns <code>true</code> if <code>Node</code> represents a
-%% proper list, and <code>false</code> otherwise. A proper list is a
-%% list skeleton either on the form "<code>[]</code>" or
+%% @doc Returns `true' if `Node' represents a
+%% proper list, and `false' otherwise. A proper list is a
+%% list skeleton either on the form "`[]'" or
%% "<code>[<em>E1</em>, ..., <em>En</em>]</code>", or "<code>[... |
-%% <em>Tail</em>]</code>" where recursively <code>Tail</code> also
+%% <em>Tail</em>]</code>" where recursively `Tail' also
%% represents a proper list.
%%
-%% <p>Note: Since <code>Node</code> is a syntax tree, the actual
+%% Note: Since `Node' is a syntax tree, the actual
%% run-time values corresponding to its subtrees may often be partially
-%% or completely unknown. Thus, if <code>Node</code> represents e.g.
-%% "<code>[... | Ns]</code>" (where <code>Ns</code> is a variable), then
-%% the function will return <code>false</code>, because it is not known
-%% whether <code>Ns</code> will be bound to a list at run-time. If
-%% <code>Node</code> instead represents e.g. "<code>[1, 2, 3]</code>" or
-%% "<code>[A | []]</code>", then the function will return
-%% <code>true</code>.</p>
+%% or completely unknown. Thus, if `Node' represents e.g.
+%% "`[... | Ns]'" (where `Ns' is a variable), then
+%% the function will return `false', because it is not known
+%% whether `Ns' will be bound to a list at run-time. If
+%% `Node' instead represents e.g. "`[1, 2, 3]'" or
+%% "`[A | []]'", then the function will return
+%% `true'.
%%
%% @see list/2
@@ -2417,14 +2248,11 @@ is_proper_list(Node) ->
%% =====================================================================
-%% @spec list_elements(Node::syntaxTree()) -> [syntaxTree()]
-%%
%% @doc Returns the list of element subtrees of a list skeleton.
-%% <code>Node</code> must represent a proper list. E.g., if
-%% <code>Node</code> represents "<code>[<em>X1</em>, <em>X2</em> |
+%% `Node' must represent a proper list. E.g., if
+%% `Node' represents "<code>[<em>X1</em>, <em>X2</em> |
%% [<em>X3</em>, <em>X4</em> | []]</code>", then
-%% <code>list_elements(Node)</code> yields the list <code>[X1, X2, X3,
-%% X4]</code>.
+%% `list_elements(Node)' yields the list `[X1, X2, X3, X4]'.
%%
%% @see list/2
%% @see is_proper_list/1
@@ -2450,17 +2278,15 @@ list_elements(Node, As) ->
%% =====================================================================
-%% @spec list_length(Node::syntaxTree()) -> integer()
-%%
%% @doc Returns the number of element subtrees of a list skeleton.
-%% <code>Node</code> must represent a proper list. E.g., if
-%% <code>Node</code> represents "<code>[X1 | [X2, X3 | [X4, X5,
-%% X6]]]</code>", then <code>list_length(Node)</code> returns the
+%% `Node' must represent a proper list. E.g., if
+%% `Node' represents "`[X1 | [X2, X3 | [X4, X5,
+%% X6]]]'", then `list_length(Node)' returns the
%% integer 6.
%%
-%% <p>Note: this is equivalent to
-%% <code>length(list_elements(Node))</code>, but potentially more
-%% efficient.</p>
+%% Note: this is equivalent to
+%% `length(list_elements(Node))', but potentially more
+%% efficient.
%%
%% @see list/2
%% @see is_proper_list/1
@@ -2487,18 +2313,16 @@ list_length(Node, A) ->
%% =====================================================================
-%% @spec normalize_list(Node::syntaxTree()) -> syntaxTree()
-%%
%% @doc Expands an abstract list skeleton to its most explicit form. If
-%% <code>Node</code> represents "<code>[<em>E1</em>, ..., <em>En</em> |
+%% `Node' represents "<code>[<em>E1</em>, ..., <em>En</em> |
%% <em>Tail</em>]</code>", the result represents "<code>[<em>E1</em> |
%% ... [<em>En</em> | <em>Tail1</em>] ... ]</code>", where
-%% <code>Tail1</code> is the result of
-%% <code>normalize_list(Tail)</code>. If <code>Node</code> represents
+%% `Tail1' is the result of
+%% `normalize_list(Tail)'. If `Node' represents
%% "<code>[<em>E1</em>, ..., <em>En</em>]</code>", the result simply
%% represents "<code>[<em>E1</em> | ... [<em>En</em> | []] ...
-%% ]</code>". If <code>Node</code> does not represent a list skeleton,
-%% <code>Node</code> itself is returned.
+%% ]</code>". If `Node' does not represent a list skeleton,
+%% `Node' itself is returned.
%%
%% @see list/2
%% @see compact_list/1
@@ -2528,16 +2352,14 @@ normalize_list_1(Es, Tail) ->
%% =====================================================================
-%% @spec compact_list(Node::syntaxTree()) -> syntaxTree()
-%%
%% @doc Yields the most compact form for an abstract list skeleton. The
%% result either represents "<code>[<em>E1</em>, ..., <em>En</em> |
-%% <em>Tail</em>]</code>", where <code>Tail</code> is not a list
+%% <em>Tail</em>]</code>", where `Tail' is not a list
%% skeleton, or otherwise simply "<code>[<em>E1</em>, ...,
-%% <em>En</em>]</code>". Annotations on subtrees of <code>Node</code>
+%% <em>En</em>]</code>". Annotations on subtrees of `Node'
%% that represent list skeletons may be lost, but comments will be
-%% propagated to the result. Returns <code>Node</code> itself if
-%% <code>Node</code> does not represent a list skeleton.
+%% propagated to the result. Returns `Node' itself if
+%% `Node' does not represent a list skeleton.
%%
%% @see list/2
%% @see normalize_list/1
@@ -2575,10 +2397,8 @@ compact_list(Node) ->
%% =====================================================================
-%% @spec binary(Fields::[syntaxTree()]) -> syntaxTree()
-%%
%% @doc Creates an abstract binary-object template. If
-%% <code>Fields</code> is <code>[F1, ..., Fn]</code>, the result
+%% `Fields' is `[F1, ..., Fn]', the result
%% represents "<code>&lt;&lt;<em>F1</em>, ...,
%% <em>Fn</em>&gt;&gt;</code>".
%%
@@ -2611,10 +2431,7 @@ revert_binary(Node) ->
%% =====================================================================
-%% @spec binary_fields(syntaxTree()) -> [syntaxTree()]
-%%
-%% @doc Returns the list of field subtrees of a <code>binary</code>
-%% node.
+%% @doc Returns the list of field subtrees of a `binary' node.
%%
%% @see binary/1
%% @see binary_field/2
@@ -2631,7 +2448,6 @@ binary_fields(Node) ->
%% =====================================================================
-%% @spec binary_field(Body) -> syntaxTree()
%% @equiv binary_field(Body, [])
-spec binary_field(syntaxTree()) -> syntaxTree().
@@ -2641,15 +2457,11 @@ binary_field(Body) ->
%% =====================================================================
-%% @spec binary_field(Body::syntaxTree(), Size,
-%% Types::[syntaxTree()]) -> syntaxTree()
-%% Size = none | syntaxTree()
-%%
%% @doc Creates an abstract binary template field.
-%% If <code>Size</code> is <code>none</code>, this is equivalent to
-%% "<code>binary_field(Body, Types)</code>", otherwise it is
-%% equivalent to "<code>binary_field(size_qualifier(Body, Size),
-%% Types)</code>".
+%% If `Size' is `none', this is equivalent to
+%% "`binary_field(Body, Types)'", otherwise it is
+%% equivalent to "`binary_field(size_qualifier(Body, Size),
+%% Types)'".
%%
%% (This is a utility function.)
%%
@@ -2667,13 +2479,10 @@ binary_field(Body, Size, Types) ->
%% =====================================================================
-%% @spec binary_field(Body::syntaxTree(), Types::[syntaxTree()]) ->
-%% syntaxTree()
-%%
%% @doc Creates an abstract binary template field. If
-%% <code>Types</code> is the empty list, the result simply represents
-%% "<code><em>Body</em></code>", otherwise, if <code>Types</code> is
-%% <code>[T1, ..., Tn]</code>, the result represents
+%% `Types' is the empty list, the result simply represents
+%% "<code><em>Body</em></code>", otherwise, if `Types' is
+%% `[T1, ..., Tn]', the result represents
%% "<code><em>Body</em>/<em>T1</em>-...-<em>Tn</em></code>".
%%
%% @see binary/1
@@ -2727,9 +2536,7 @@ revert_binary_field(Node) ->
%% =====================================================================
-%% @spec binary_field_body(syntaxTree()) -> syntaxTree()
-%%
-%% @doc Returns the body subtree of a <code>binary_field</code>.
+%% @doc Returns the body subtree of a `binary_field'.
%%
%% @see binary_field/2
@@ -2749,12 +2556,10 @@ binary_field_body(Node) ->
%% =====================================================================
-%% @spec binary_field_types(Node::syntaxTree()) -> [syntaxTree()]
-%%
%% @doc Returns the list of type-specifier subtrees of a
-%% <code>binary_field</code> node. If <code>Node</code> represents
+%% `binary_field' node. If `Node' represents
%% "<code>.../<em>T1</em>, ..., <em>Tn</em></code>", the result is
-%% <code>[T1, ..., Tn]</code>, otherwise the result is the empty list.
+%% `[T1, ..., Tn]', otherwise the result is the empty list.
%%
%% @see binary_field/2
@@ -2774,14 +2579,12 @@ binary_field_types(Node) ->
%% =====================================================================
-%% @spec binary_field_size(Node::syntaxTree()) -> none | syntaxTree()
-%%
%% @doc Returns the size specifier subtree of a
-%% <code>binary_field</code> node, if any. If <code>Node</code>
+%% `binary_field' node, if any. If `Node'
%% represents "<code><em>Body</em>:<em>Size</em></code>" or
%% "<code><em>Body</em>:<em>Size</em>/<em>T1</em>, ...,
-%% <em>Tn</em></code>", the result is <code>Size</code>, otherwise
-%% <code>none</code> is returned.
+%% <em>Tn</em></code>", the result is `Size', otherwise
+%% `none' is returned.
%%
%% (This is a utility function.)
%%
@@ -2810,9 +2613,6 @@ binary_field_size(Node) ->
%% =====================================================================
-%% @spec size_qualifier(Body::syntaxTree(), Size::syntaxTree()) ->
-%% syntaxTree()
-%%
%% @doc Creates an abstract size qualifier. The result represents
%% "<code><em>Body</em>:<em>Size</em></code>".
%%
@@ -2834,10 +2634,7 @@ size_qualifier(Body, Size) ->
%% =====================================================================
-%% @spec size_qualifier_body(syntaxTree()) -> syntaxTree()
-%%
-%% @doc Returns the body subtree of a <code>size_qualifier</code>
-%% node.
+%% @doc Returns the body subtree of a `size_qualifier' node.
%%
%% @see size_qualifier/2
@@ -2848,10 +2645,8 @@ size_qualifier_body(Node) ->
%% =====================================================================
-%% @spec size_qualifier_argument(syntaxTree()) -> syntaxTree()
-%%
%% @doc Returns the argument subtree (the size) of a
-%% <code>size_qualifier</code> node.
+%% `size_qualifier' node.
%%
%% @see size_qualifier/2
@@ -2862,16 +2657,14 @@ size_qualifier_argument(Node) ->
%% =====================================================================
-%% @spec error_marker(Error::term()) -> syntaxTree()
-%%
%% @doc Creates an abstract error marker. The result represents an
%% occurrence of an error in the source code, with an associated Erlang
-%% I/O ErrorInfo structure given by <code>Error</code> (see module
+%% I/O ErrorInfo structure given by `Error' (see module
%% {@link //stdlib/io} for details). Error markers are regarded as source
%% code forms, but have no defined lexical form.
%%
-%% <p>Note: this is supported only for backwards compatibility with
-%% existing parsers and tools.</p>
+%% Note: this is supported only for backwards compatibility with
+%% existing parsers and tools.
%%
%% @see error_marker_info/1
%% @see warning_marker/1
@@ -2902,10 +2695,7 @@ revert_error_marker(Node) ->
%% =====================================================================
-%% @spec error_marker_info(syntaxTree()) -> term()
-%%
-%% @doc Returns the ErrorInfo structure of an <code>error_marker</code>
-%% node.
+%% @doc Returns the ErrorInfo structure of an `error_marker' node.
%%
%% @see error_marker/1
@@ -2921,16 +2711,14 @@ error_marker_info(Node) ->
%% =====================================================================
-%% @spec warning_marker(Error::term()) -> syntaxTree()
-%%
%% @doc Creates an abstract warning marker. The result represents an
%% occurrence of a possible problem in the source code, with an
-%% associated Erlang I/O ErrorInfo structure given by <code>Error</code>
+%% associated Erlang I/O ErrorInfo structure given by `Error'
%% (see module {@link //stdlib/io} for details). Warning markers are
%% regarded as source code forms, but have no defined lexical form.
%%
-%% <p>Note: this is supported only for backwards compatibility with
-%% existing parsers and tools.</p>
+%% Note: this is supported only for backwards compatibility with
+%% existing parsers and tools.
%%
%% @see warning_marker_info/1
%% @see error_marker/1
@@ -2961,10 +2749,7 @@ revert_warning_marker(Node) ->
%% =====================================================================
-%% @spec warning_marker_info(syntaxTree()) -> term()
-%%
-%% @doc Returns the ErrorInfo structure of a <code>warning_marker</code>
-%% node.
+%% @doc Returns the ErrorInfo structure of a `warning_marker' node.
%%
%% @see warning_marker/1
@@ -2980,16 +2765,14 @@ warning_marker_info(Node) ->
%% =====================================================================
-%% @spec eof_marker() -> syntaxTree()
-%%
%% @doc Creates an abstract end-of-file marker. This represents the
%% end of input when reading a sequence of source code forms. An
%% end-of-file marker is itself regarded as a source code form
%% (namely, the last in any sequence in which it occurs). It has no
%% defined lexical form.
%%
-%% <p>Note: this is retained only for backwards compatibility with
-%% existing parsers and tools.</p>
+%% Note: this is retained only for backwards compatibility with
+%% existing parsers and tools.
%%
%% @see error_marker/1
%% @see warning_marker/1
@@ -3013,7 +2796,6 @@ revert_eof_marker(Node) ->
%% =====================================================================
-%% @spec attribute(Name) -> syntaxTree()
%% @equiv attribute(Name, none)
-spec attribute(syntaxTree()) -> syntaxTree().
@@ -3023,23 +2805,20 @@ attribute(Name) ->
%% =====================================================================
-%% @spec attribute(Name::syntaxTree(), Arguments) -> syntaxTree()
-%% Arguments = none | [syntaxTree()]
-%%
%% @doc Creates an abstract program attribute. If
-%% <code>Arguments</code> is <code>[A1, ..., An]</code>, the result
+%% `Arguments' is `[A1, ..., An]', the result
%% represents "<code>-<em>Name</em>(<em>A1</em>, ...,
-%% <em>An</em>).</code>". Otherwise, if <code>Arguments</code> is
-%% <code>none</code>, the result represents
+%% <em>An</em>).</code>". Otherwise, if `Arguments' is
+%% `none', the result represents
%% "<code>-<em>Name</em>.</code>". The latter form makes it possible
%% to represent preprocessor directives such as
-%% "<code>-endif.</code>". Attributes are source code forms.
+%% "`-endif.'". Attributes are source code forms.
%%
-%% <p>Note: The preprocessor macro definition directive
+%% Note: The preprocessor macro definition directive
%% "<code>-define(<em>Name</em>, <em>Body</em>).</code>" has relatively
-%% few requirements on the syntactical form of <code>Body</code> (viewed
-%% as a sequence of tokens). The <code>text</code> node type can be used
-%% for a <code>Body</code> that is not a normal Erlang construct.</p>
+%% few requirements on the syntactical form of `Body' (viewed
+%% as a sequence of tokens). The `text' node type can be used
+%% for a `Body' that is not a normal Erlang construct.
%%
%% @see attribute/1
%% @see attribute_name/1
@@ -3233,9 +3012,7 @@ revert_module_name(A) ->
%% =====================================================================
-%% @spec attribute_name(syntaxTree()) -> syntaxTree()
-%%
-%% @doc Returns the name subtree of an <code>attribute</code> node.
+%% @doc Returns the name subtree of an `attribute' node.
%%
%% @see attribute/1
@@ -3251,15 +3028,12 @@ attribute_name(Node) ->
%% =====================================================================
-%% @spec attribute_arguments(Node::syntaxTree()) ->
-%% none | [syntaxTree()]
-%%
%% @doc Returns the list of argument subtrees of an
-%% <code>attribute</code> node, if any. If <code>Node</code>
+%% `attribute' node, if any. If `Node'
%% represents "<code>-<em>Name</em>.</code>", the result is
-%% <code>none</code>. Otherwise, if <code>Node</code> represents
+%% `none'. Otherwise, if `Node' represents
%% "<code>-<em>Name</em>(<em>E1</em>, ..., <em>En</em>).</code>",
-%% <code>[E1, ..., E1]</code> is returned.
+%% `[E1, ..., E1]' is returned.
%%
%% @see attribute/1
@@ -3326,9 +3100,6 @@ attribute_arguments(Node) ->
%% =====================================================================
-%% @spec arity_qualifier(Body::syntaxTree(), Arity::syntaxTree()) ->
-%% syntaxTree()
-%%
%% @doc Creates an abstract arity qualifier. The result represents
%% "<code><em>Body</em>/<em>Arity</em></code>".
%%
@@ -3350,10 +3121,7 @@ arity_qualifier(Body, Arity) ->
%% =====================================================================
-%% @spec arity_qualifier_body(syntaxTree()) -> syntaxTree()
-%%
-%% @doc Returns the body subtree of an <code>arity_qualifier</code>
-%% node.
+%% @doc Returns the body subtree of an `arity_qualifier' node.
%%
%% @see arity_qualifier/2
@@ -3364,10 +3132,8 @@ arity_qualifier_body(Node) ->
%% =====================================================================
-%% @spec arity_qualifier_argument(syntaxTree()) -> syntaxTree()
-%%
%% @doc Returns the argument (the arity) subtree of an
-%% <code>arity_qualifier</code> node.
+%% `arity_qualifier' node.
%%
%% @see arity_qualifier/2
@@ -3378,9 +3144,6 @@ arity_qualifier_argument(Node) ->
%% =====================================================================
-%% @spec module_qualifier(Module::syntaxTree(), Body::syntaxTree()) ->
-%% syntaxTree()
-%%
%% @doc Creates an abstract module qualifier. The result represents
%% "<code><em>Module</em>:<em>Body</em></code>".
%%
@@ -3414,10 +3177,8 @@ revert_module_qualifier(Node) ->
%% =====================================================================
-%% @spec module_qualifier_argument(syntaxTree()) -> syntaxTree()
-%%
%% @doc Returns the argument (the module) subtree of a
-%% <code>module_qualifier</code> node.
+%% `module_qualifier' node.
%%
%% @see module_qualifier/2
@@ -3433,10 +3194,7 @@ module_qualifier_argument(Node) ->
%% =====================================================================
-%% @spec module_qualifier_body(syntaxTree()) -> syntaxTree()
-%%
-%% @doc Returns the body subtree of a <code>module_qualifier</code>
-%% node.
+%% @doc Returns the body subtree of a `module_qualifier' node.
%%
%% @see module_qualifier/2
@@ -3452,11 +3210,9 @@ module_qualifier_body(Node) ->
%% =====================================================================
-%% @spec qualified_name(Segments::[syntaxTree()]) -> syntaxTree()
-%%
%% @doc Creates an abstract qualified name. The result represents
%% "<code><em>S1</em>.<em>S2</em>. ... .<em>Sn</em></code>", if
-%% <code>Segments</code> is <code>[S1, S2, ..., Sn]</code>.
+%% `Segments' is `[S1, S2, ..., Sn]'.
%%
%% @see qualified_name_segments/1
@@ -3484,10 +3240,8 @@ revert_qualified_name(Node) ->
%% =====================================================================
-%% @spec qualified_name_segments(syntaxTree()) -> [syntaxTree()]
-%%
%% @doc Returns the list of name segments of a
-%% <code>qualified_name</code> node.
+%% `qualified_name' node.
%%
%% @see qualified_name/1
@@ -3503,13 +3257,10 @@ qualified_name_segments(Node) ->
%% =====================================================================
-%% @spec function(Name::syntaxTree(), Clauses::[syntaxTree()]) ->
-%% syntaxTree()
-%%
-%% @doc Creates an abstract function definition. If <code>Clauses</code>
-%% is <code>[C1, ..., Cn]</code>, the result represents
+%% @doc Creates an abstract function definition. If `Clauses'
+%% is `[C1, ..., Cn]', the result represents
%% "<code><em>Name</em> <em>C1</em>; ...; <em>Name</em>
-%% <em>Cn</em>.</code>". More exactly, if each <code>Ci</code>
+%% <em>Cn</em>.</code>". More exactly, if each `Ci'
%% represents "<code>(<em>Pi1</em>, ..., <em>Pim</em>) <em>Gi</em> ->
%% <em>Bi</em></code>", then the result represents
%% "<code><em>Name</em>(<em>P11</em>, ..., <em>P1m</em>) <em>G1</em> ->
@@ -3523,13 +3274,12 @@ qualified_name_segments(Node) ->
%% @see is_form/1
%% @see rule/2
--record(function, {name, clauses}).
-%% XXX: This one is problematic because there is a tuple with the same
-%% tag and size that comes from 'erl_parse'
-%% -record(function, {name :: syntaxTree(), clauses :: [syntaxTree()]}).
+%% Don't use the name 'function' for this record, to avoid confusion with
+%% the tuples on the form {function,Name,Arity} used by erl_parse.
+-record(func, {name :: syntaxTree(), clauses :: [syntaxTree()]}).
%% type(Node) = function
-%% data(Node) = #function{name :: Name, clauses :: Clauses}
+%% data(Node) = #func{name :: Name, clauses :: Clauses}
%%
%% Name = syntaxTree()
%% Clauses = [syntaxTree()]
@@ -3556,7 +3306,7 @@ qualified_name_segments(Node) ->
-spec function(syntaxTree(), [syntaxTree()]) -> syntaxTree().
function(Name, Clauses) ->
- tree(function, #function{name = Name, clauses = Clauses}).
+ tree(function, #func{name = Name, clauses = Clauses}).
revert_function(Node) ->
Name = function_name(Node),
@@ -3572,9 +3322,7 @@ revert_function(Node) ->
%% =====================================================================
-%% @spec function_name(syntaxTree()) -> syntaxTree()
-%%
-%% @doc Returns the name subtree of a <code>function</code> node.
+%% @doc Returns the name subtree of a `function' node.
%%
%% @see function/2
@@ -3585,15 +3333,12 @@ function_name(Node) ->
{function, Pos, Name, _, _} ->
set_pos(atom(Name), Pos);
Node1 ->
- (data(Node1))#function.name
+ (data(Node1))#func.name
end.
%% =====================================================================
-%% @spec function_clauses(syntaxTree()) -> [syntaxTree()]
-%%
-%% @doc Returns the list of clause subtrees of a <code>function</code>
-%% node.
+%% @doc Returns the list of clause subtrees of a `function' node.
%%
%% @see function/2
@@ -3604,21 +3349,19 @@ function_clauses(Node) ->
{function, _, _, _, Clauses} ->
Clauses;
Node1 ->
- (data(Node1))#function.clauses
+ (data(Node1))#func.clauses
end.
%% =====================================================================
-%% @spec function_arity(Node::syntaxTree()) -> integer()
-%%
-%% @doc Returns the arity of a <code>function</code> node. The result
+%% @doc Returns the arity of a `function' node. The result
%% is the number of parameter patterns in the first clause of the
%% function; subsequent clauses are ignored.
%%
-%% <p>An exception is thrown if <code>function_clauses(Node)</code>
+%% An exception is thrown if `function_clauses(Node)'
%% returns an empty list, or if the first element of that list is not
-%% a syntax tree <code>C</code> of type <code>clause</code> such that
-%% <code>clause_patterns(C)</code> is a nonempty list.</p>
+%% a syntax tree `C' of type `clause' such that
+%% `clause_patterns(C)' is a nonempty list.
%%
%% @see function/2
%% @see function_clauses/1
@@ -3634,7 +3377,6 @@ function_arity(Node) ->
%% =====================================================================
-%% @spec clause(Guard, Body) -> syntaxTree()
%% @equiv clause([], Guard, Body)
-type guard() :: 'none' | syntaxTree() | [syntaxTree()] | [[syntaxTree()]].
@@ -3646,34 +3388,28 @@ clause(Guard, Body) ->
%% =====================================================================
-%% @spec clause(Patterns::[syntaxTree()], Guard,
-%% Body::[syntaxTree()]) -> syntaxTree()
-%% Guard = none | syntaxTree()
-%% | [syntaxTree()] | [[syntaxTree()]]
-%%
-%% @doc Creates an abstract clause. If <code>Patterns</code> is
-%% <code>[P1, ..., Pn]</code> and <code>Body</code> is <code>[B1, ...,
-%% Bm]</code>, then if <code>Guard</code> is <code>none</code>, the
+%% @doc Creates an abstract clause. If `Patterns' is
+%% `[P1, ..., Pn]' and `Body' is `[B1, ...,
+%% Bm]', then if `Guard' is `none', the
%% result represents "<code>(<em>P1</em>, ..., <em>Pn</em>) ->
%% <em>B1</em>, ..., <em>Bm</em></code>", otherwise, unless
-%% <code>Guard</code> is a list, the result represents
+%% `Guard' is a list, the result represents
%% "<code>(<em>P1</em>, ..., <em>Pn</em>) when <em>Guard</em> ->
%% <em>B1</em>, ..., <em>Bm</em></code>".
%%
-%% <p>For simplicity, the <code>Guard</code> argument may also be any
+%% For simplicity, the `Guard' argument may also be any
%% of the following:
%% <ul>
-%% <li>An empty list <code>[]</code>. This is equivalent to passing
-%% <code>none</code>.</li>
-%% <li>A nonempty list <code>[E1, ..., Ej]</code> of syntax trees.
-%% This is equivalent to passing <code>conjunction([E1, ...,
-%% Ej])</code>.</li>
-%% <li>A nonempty list of lists of syntax trees <code>[[E1_1, ...,
-%% E1_k1], ..., [Ej_1, ..., Ej_kj]]</code>, which is equivalent
-%% to passing <code>disjunction([conjunction([E1_1, ...,
-%% E1_k1]), ..., conjunction([Ej_1, ..., Ej_kj])])</code>.</li>
+%% <li>An empty list `[]'. This is equivalent to passing
+%% `none'.</li>
+%% <li>A nonempty list `[E1, ..., Ej]' of syntax trees.
+%% This is equivalent to passing `conjunction([E1, ...,
+%% Ej])'.</li>
+%% <li>A nonempty list of lists of syntax trees `[[E1_1, ...,
+%% E1_k1], ..., [Ej_1, ..., Ej_kj]]', which is equivalent
+%% to passing `disjunction([conjunction([E1_1, ...,
+%% E1_k1]), ..., conjunction([Ej_1, ..., Ej_kj])])'.</li>
%% </ul>
-%% </p>
%%
%% @see clause/2
%% @see clause_patterns/1
@@ -3789,10 +3525,7 @@ unfold_try_clause({clause, Pos, [{tuple, _, [C, V, _]}],
%% =====================================================================
-%% @spec clause_patterns(syntaxTree()) -> [syntaxTree()]
-%%
-%% @doc Returns the list of pattern subtrees of a <code>clause</code>
-%% node.
+%% @doc Returns the list of pattern subtrees of a `clause' node.
%%
%% @see clause/3
@@ -3808,13 +3541,11 @@ clause_patterns(Node) ->
%% =====================================================================
-%% @spec clause_guard(Node::syntaxTree()) -> none | syntaxTree()
-%%
-%% @doc Returns the guard subtree of a <code>clause</code> node, if
-%% any. If <code>Node</code> represents "<code>(<em>P1</em>, ...,
+%% @doc Returns the guard subtree of a `clause' node, if
+%% any. If `Node' represents "<code>(<em>P1</em>, ...,
%% <em>Pn</em>) when <em>Guard</em> -> <em>B1</em>, ...,
-%% <em>Bm</em></code>", <code>Guard</code> is returned. Otherwise, the
-%% result is <code>none</code>.
+%% <em>Bm</em></code>", `Guard' is returned. Otherwise, the
+%% result is `none'.
%%
%% @see clause/3
@@ -3836,10 +3567,7 @@ clause_guard(Node) ->
%% =====================================================================
-%% @spec clause_body(syntaxTree()) -> [syntaxTree()]
-%%
-%% @doc Return the list of body subtrees of a <code>clause</code>
-%% node.
+%% @doc Return the list of body subtrees of a `clause' node.
%%
%% @see clause/3
@@ -3855,10 +3583,8 @@ clause_body(Node) ->
%% =====================================================================
-%% @spec disjunction(List::[syntaxTree()]) -> syntaxTree()
-%%
-%% @doc Creates an abstract disjunction. If <code>List</code> is
-%% <code>[E1, ..., En]</code>, the result represents
+%% @doc Creates an abstract disjunction. If `List' is
+%% `[E1, ..., En]', the result represents
%% "<code><em>E1</em>; ...; <em>En</em></code>".
%%
%% @see disjunction_body/1
@@ -3874,10 +3600,8 @@ disjunction(Tests) ->
%% =====================================================================
-%% @spec disjunction_body(syntaxTree()) -> [syntaxTree()]
-%%
%% @doc Returns the list of body subtrees of a
-%% <code>disjunction</code> node.
+%% `disjunction' node.
%%
%% @see disjunction/1
@@ -3888,10 +3612,8 @@ disjunction_body(Node) ->
%% =====================================================================
-%% @spec conjunction(List::[syntaxTree()]) -> syntaxTree()
-%%
-%% @doc Creates an abstract conjunction. If <code>List</code> is
-%% <code>[E1, ..., En]</code>, the result represents
+%% @doc Creates an abstract conjunction. If `List' is
+%% `[E1, ..., En]', the result represents
%% "<code><em>E1</em>, ..., <em>En</em></code>".
%%
%% @see conjunction_body/1
@@ -3907,10 +3629,8 @@ conjunction(Tests) ->
%% =====================================================================
-%% @spec conjunction_body(syntaxTree()) -> [syntaxTree()]
-%%
%% @doc Returns the list of body subtrees of a
-%% <code>conjunction</code> node.
+%% `conjunction' node.
%%
%% @see conjunction/1
@@ -3921,8 +3641,6 @@ conjunction_body(Node) ->
%% =====================================================================
-%% @spec catch_expr(Expr::syntaxTree()) -> syntaxTree()
-%%
%% @doc Creates an abstract catch-expression. The result represents
%% "<code>catch <em>Expr</em></code>".
%%
@@ -3949,9 +3667,7 @@ revert_catch_expr(Node) ->
%% =====================================================================
-%% @spec catch_expr_body(syntaxTree()) -> syntaxTree()
-%%
-%% @doc Returns the body subtree of a <code>catch_expr</code> node.
+%% @doc Returns the body subtree of a `catch_expr' node.
%%
%% @see catch_expr/1
@@ -3967,9 +3683,6 @@ catch_expr_body(Node) ->
%% =====================================================================
-%% @spec match_expr(Pattern::syntaxTree(), Body::syntaxTree()) ->
-%% syntaxTree()
-%%
%% @doc Creates an abstract match-expression. The result represents
%% "<code><em>Pattern</em> = <em>Body</em></code>".
%%
@@ -4002,9 +3715,7 @@ revert_match_expr(Node) ->
%% =====================================================================
-%% @spec match_expr_pattern(syntaxTree()) -> syntaxTree()
-%%
-%% @doc Returns the pattern subtree of a <code>match_expr</code> node.
+%% @doc Returns the pattern subtree of a `match_expr' node.
%%
%% @see match_expr/2
@@ -4020,9 +3731,7 @@ match_expr_pattern(Node) ->
%% =====================================================================
-%% @spec match_expr_body(syntaxTree()) -> syntaxTree()
-%%
-%% @doc Returns the body subtree of a <code>match_expr</code> node.
+%% @doc Returns the body subtree of a `match_expr' node.
%%
%% @see match_expr/2
@@ -4038,15 +3747,12 @@ match_expr_body(Node) ->
%% =====================================================================
-%% @spec operator(Name) -> syntaxTree()
-%% Name = atom() | string()
-%%
%% @doc Creates an abstract operator. The name of the operator is the
-%% character sequence represented by <code>Name</code>. This is
+%% character sequence represented by `Name'. This is
%% analogous to the print name of an atom, but an operator is never
%% written within single-quotes; e.g., the result of
-%% <code>operator('++')</code> represents "<code>++</code>" rather
-%% than "<code>'++'</code>".
+%% `operator('++')' represents "`++'" rather
+%% than "`'++''".
%%
%% @see operator_name/1
%% @see operator_literal/1
@@ -4064,9 +3770,7 @@ operator(Name) ->
%% =====================================================================
-%% @spec operator_name(syntaxTree()) -> atom()
-%%
-%% @doc Returns the name of an <code>operator</code> node. Note that
+%% @doc Returns the name of an `operator' node. Note that
%% the name is returned as an atom.
%%
%% @see operator/1
@@ -4078,11 +3782,8 @@ operator_name(Node) ->
%% =====================================================================
-%% @spec operator_literal(syntaxTree()) -> string()
-%%
%% @doc Returns the literal string represented by an
-%% <code>operator</code> node. This is simply the operator name as a
-%% string.
+%% `operator' node. This is simply the operator name as a string.
%%
%% @see operator/1
@@ -4093,9 +3794,6 @@ operator_literal(Node) ->
%% =====================================================================
-%% @spec infix_expr(Left::syntaxTree(), Operator::syntaxTree(),
-%% Right::syntaxTree()) -> syntaxTree()
-%%
%% @doc Creates an abstract infix operator expression. The result
%% represents "<code><em>Left</em> <em>Operator</em>
%% <em>Right</em></code>".
@@ -4144,10 +3842,8 @@ revert_infix_expr(Node) ->
%% =====================================================================
-%% @spec infix_expr_left(syntaxTree()) -> syntaxTree()
-%%
%% @doc Returns the left argument subtree of an
-%% <code>infix_expr</code> node.
+%% `infix_expr' node.
%%
%% @see infix_expr/3
@@ -4163,10 +3859,7 @@ infix_expr_left(Node) ->
%% =====================================================================
-%% @spec infix_expr_operator(syntaxTree()) -> syntaxTree()
-%%
-%% @doc Returns the operator subtree of an <code>infix_expr</code>
-%% node.
+%% @doc Returns the operator subtree of an `infix_expr' node.
%%
%% @see infix_expr/3
@@ -4182,10 +3875,8 @@ infix_expr_operator(Node) ->
%% =====================================================================
-%% @spec infix_expr_right(syntaxTree()) -> syntaxTree()
-%%
%% @doc Returns the right argument subtree of an
-%% <code>infix_expr</code> node.
+%% `infix_expr' node.
%%
%% @see infix_expr/3
@@ -4201,9 +3892,6 @@ infix_expr_right(Node) ->
%% =====================================================================
-%% @spec prefix_expr(Operator::syntaxTree(), Argument::syntaxTree()) ->
-%% syntaxTree()
-%%
%% @doc Creates an abstract prefix operator expression. The result
%% represents "<code><em>Operator</em> <em>Argument</em></code>".
%%
@@ -4247,10 +3935,7 @@ revert_prefix_expr(Node) ->
%% =====================================================================
-%% @spec prefix_expr_operator(syntaxTree()) -> syntaxTree()
-%%
-%% @doc Returns the operator subtree of a <code>prefix_expr</code>
-%% node.
+%% @doc Returns the operator subtree of a `prefix_expr' node.
%%
%% @see prefix_expr/2
@@ -4266,10 +3951,7 @@ prefix_expr_operator(Node) ->
%% =====================================================================
-%% @spec prefix_expr_argument(syntaxTree()) -> syntaxTree()
-%%
-%% @doc Returns the argument subtree of a <code>prefix_expr</code>
-%% node.
+%% @doc Returns the argument subtree of a `prefix_expr' node.
%%
%% @see prefix_expr/2
@@ -4285,7 +3967,6 @@ prefix_expr_argument(Node) ->
%% =====================================================================
-%% @spec record_field(Name) -> syntaxTree()
%% @equiv record_field(Name, none)
-spec record_field(syntaxTree()) -> syntaxTree().
@@ -4295,11 +3976,8 @@ record_field(Name) ->
%% =====================================================================
-%% @spec record_field(Name::syntaxTree(), Value) -> syntaxTree()
-%% Value = none | syntaxTree()
-%%
%% @doc Creates an abstract record field specification. If
-%% <code>Value</code> is <code>none</code>, the result represents
+%% `Value' is `none', the result represents
%% simply "<code><em>Name</em></code>", otherwise it represents
%% "<code><em>Name</em> = <em>Value</em></code>".
%%
@@ -4321,9 +3999,7 @@ record_field(Name, Value) ->
%% =====================================================================
-%% @spec record_field_name(syntaxTree()) -> syntaxTree()
-%%
-%% @doc Returns the name subtree of a <code>record_field</code> node.
+%% @doc Returns the name subtree of a `record_field' node.
%%
%% @see record_field/2
@@ -4334,13 +4010,11 @@ record_field_name(Node) ->
%% =====================================================================
-%% @spec record_field_value(syntaxTree()) -> none | syntaxTree()
-%%
-%% @doc Returns the value subtree of a <code>record_field</code> node,
-%% if any. If <code>Node</code> represents
-%% "<code><em>Name</em></code>", <code>none</code> is
-%% returned. Otherwise, if <code>Node</code> represents
-%% "<code><em>Name</em> = <em>Value</em></code>", <code>Value</code>
+%% @doc Returns the value subtree of a `record_field' node,
+%% if any. If `Node' represents
+%% "<code><em>Name</em></code>", `none' is
+%% returned. Otherwise, if `Node' represents
+%% "<code><em>Name</em> = <em>Value</em></code>", `Value'
%% is returned.
%%
%% @see record_field/2
@@ -4352,15 +4026,12 @@ record_field_value(Node) ->
%% =====================================================================
-%% @spec record_index_expr(Type::syntaxTree(), Field::syntaxTree()) ->
-%% syntaxTree()
-%%
%% @doc Creates an abstract record field index expression. The result
%% represents "<code>#<em>Type</em>.<em>Field</em></code>".
%%
-%% <p>(Note: the function name <code>record_index/2</code> is reserved
+%% (Note: the function name `record_index/2' is reserved
%% by the Erlang compiler, which is why that name could not be used
-%% for this constructor.)</p>
+%% for this constructor.)
%%
%% @see record_index_expr_type/1
%% @see record_index_expr_field/1
@@ -4399,10 +4070,7 @@ revert_record_index_expr(Node) ->
%% =====================================================================
-%% @spec record_index_expr_type(syntaxTree()) -> syntaxTree()
-%%
-%% @doc Returns the type subtree of a <code>record_index_expr</code>
-%% node.
+%% @doc Returns the type subtree of a `record_index_expr' node.
%%
%% @see record_index_expr/2
@@ -4418,10 +4086,7 @@ record_index_expr_type(Node) ->
%% =====================================================================
-%% @spec record_index_expr_field(syntaxTree()) -> syntaxTree()
-%%
-%% @doc Returns the field subtree of a <code>record_index_expr</code>
-%% node.
+%% @doc Returns the field subtree of a `record_index_expr' node.
%%
%% @see record_index_expr/2
@@ -4437,7 +4102,6 @@ record_index_expr_field(Node) ->
%% =====================================================================
-%% @spec record_access(Argument, Field) -> syntaxTree()
%% @equiv record_access(Argument, none, Field)
-spec record_access(syntaxTree(), syntaxTree()) -> syntaxTree().
@@ -4447,17 +4111,13 @@ record_access(Argument, Field) ->
%% =====================================================================
-%% @spec record_access(Argument::syntaxTree(), Type,
-%% Field::syntaxTree()) -> syntaxTree()
-%% Type = none | syntaxTree()
-%%
%% @doc Creates an abstract record field access expression. If
-%% <code>Type</code> is not <code>none</code>, the result represents
+%% `Type' is not `none', the result represents
%% "<code><em>Argument</em>#<em>Type</em>.<em>Field</em></code>".
%%
-%% <p>If <code>Type</code> is <code>none</code>, the result represents
+%% If `Type' is `none', the result represents
%% "<code><em>Argument</em>.<em>Field</em></code>". This is a special
-%% form only allowed within Mnemosyne queries.</p>
+%% form only allowed within Mnemosyne queries.
%%
%% @see record_access/2
%% @see record_access_argument/1
@@ -4512,10 +4172,7 @@ revert_record_access(Node) ->
%% =====================================================================
-%% @spec record_access_argument(syntaxTree()) -> syntaxTree()
-%%
-%% @doc Returns the argument subtree of a <code>record_access</code>
-%% node.
+%% @doc Returns the argument subtree of a `record_access' node.
%%
%% @see record_access/3
@@ -4533,14 +4190,12 @@ record_access_argument(Node) ->
%% =====================================================================
-%% @spec record_access_type(syntaxTree()) -> none | syntaxTree()
-%%
-%% @doc Returns the type subtree of a <code>record_access</code> node,
-%% if any. If <code>Node</code> represents
-%% "<code><em>Argument</em>.<em>Field</em></code>", <code>none</code>
-%% is returned, otherwise if <code>Node</code> represents
+%% @doc Returns the type subtree of a `record_access' node,
+%% if any. If `Node' represents
+%% "<code><em>Argument</em>.<em>Field</em></code>", `none'
+%% is returned, otherwise if `Node' represents
%% "<code><em>Argument</em>#<em>Type</em>.<em>Field</em></code>",
-%% <code>Type</code> is returned.
+%% `Type' is returned.
%%
%% @see record_access/3
@@ -4558,10 +4213,7 @@ record_access_type(Node) ->
%% =====================================================================
-%% @spec record_access_field(syntaxTree()) -> syntaxTree()
-%%
-%% @doc Returns the field subtree of a <code>record_access</code>
-%% node.
+%% @doc Returns the field subtree of a `record_access' node.
%%
%% @see record_access/3
@@ -4579,7 +4231,6 @@ record_access_field(Node) ->
%% =====================================================================
-%% @spec record_expr(Type, Fields) -> syntaxTree()
%% @equiv record_expr(none, Type, Fields)
-spec record_expr(syntaxTree(), [syntaxTree()]) -> syntaxTree().
@@ -4589,13 +4240,9 @@ record_expr(Type, Fields) ->
%% =====================================================================
-%% @spec record_expr(Argument, Type::syntaxTree(),
-%% Fields::[syntaxTree()]) -> syntaxTree()
-%% Argument = none | syntaxTree()
-%%
-%% @doc Creates an abstract record expression. If <code>Fields</code> is
-%% <code>[F1, ..., Fn]</code>, then if <code>Argument</code> is
-%% <code>none</code>, the result represents
+%% @doc Creates an abstract record expression. If `Fields' is
+%% `[F1, ..., Fn]', then if `Argument' is
+%% `none', the result represents
%% "<code>#<em>Type</em>{<em>F1</em>, ..., <em>Fn</em>}</code>",
%% otherwise it represents
%% "<code><em>Argument</em>#<em>Type</em>{<em>F1</em>, ...,
@@ -4661,14 +4308,12 @@ revert_record_expr(Node) ->
%% =====================================================================
-%% @spec record_expr_argument(syntaxTree()) -> none | syntaxTree()
-%%
-%% @doc Returns the argument subtree of a <code>record_expr</code> node,
-%% if any. If <code>Node</code> represents
-%% "<code>#<em>Type</em>{...}</code>", <code>none</code> is returned.
-%% Otherwise, if <code>Node</code> represents
+%% @doc Returns the argument subtree of a `record_expr' node,
+%% if any. If `Node' represents
+%% "<code>#<em>Type</em>{...}</code>", `none' is returned.
+%% Otherwise, if `Node' represents
%% "<code><em>Argument</em>#<em>Type</em>{...}</code>",
-%% <code>Argument</code> is returned.
+%% `Argument' is returned.
%%
%% @see record_expr/3
@@ -4686,9 +4331,7 @@ record_expr_argument(Node) ->
%% =====================================================================
-%% @spec record_expr_type(syntaxTree()) -> syntaxTree()
-%%
-%% @doc Returns the type subtree of a <code>record_expr</code> node.
+%% @doc Returns the type subtree of a `record_expr' node.
%%
%% @see record_expr/3
@@ -4706,10 +4349,8 @@ record_expr_type(Node) ->
%% =====================================================================
-%% @spec record_expr_fields(syntaxTree()) -> [syntaxTree()]
-%%
%% @doc Returns the list of field subtrees of a
-%% <code>record_expr</code> node.
+%% `record_expr' node.
%%
%% @see record_expr/3
@@ -4727,15 +4368,11 @@ record_expr_fields(Node) ->
%% =====================================================================
-%% @spec application(Module, Function::syntaxTree(),
-%% Arguments::[syntaxTree()]) -> syntaxTree()
-%% Module = none | syntaxTree()
-%%
%% @doc Creates an abstract function application expression. If
-%% <code>Module</code> is <code>none</code>, this is call is equivalent
-%% to <code>application(Function, Arguments)</code>, otherwise it is
-%% equivalent to <code>application(module_qualifier(Module, Function),
-%% Arguments)</code>.
+%% `Module' is `none', this is call is equivalent
+%% to `application(Function, Arguments)', otherwise it is
+%% equivalent to `application(module_qualifier(Module, Function),
+%% Arguments)'.
%%
%% (This is a utility function.)
%%
@@ -4752,11 +4389,8 @@ application(Module, Name, Arguments) ->
%% =====================================================================
-%% @spec application(Operator::syntaxTree(),
-%% Arguments::[syntaxTree()]) -> syntaxTree()
-%%
%% @doc Creates an abstract function application expression. If
-%% <code>Arguments</code> is <code>[A1, ..., An]</code>, the result
+%% `Arguments' is `[A1, ..., An]', the result
%% represents "<code><em>Operator</em>(<em>A1</em>, ...,
%% <em>An</em>)</code>".
%%
@@ -4794,14 +4428,11 @@ revert_application(Node) ->
%% =====================================================================
-%% @spec application_operator(syntaxTree()) -> syntaxTree()
+%% @doc Returns the operator subtree of an `application' node.
%%
-%% @doc Returns the operator subtree of an <code>application</code>
-%% node.
-%%
-%% <p>Note: if <code>Node</code> represents
+%% Note: if `Node' represents
%% "<code><em>M</em>:<em>F</em>(...)</code>", then the result is the
-%% subtree representing "<code><em>M</em>:<em>F</em></code>".</p>
+%% subtree representing "<code><em>M</em>:<em>F</em></code>".
%%
%% @see application/2
%% @see module_qualifier/2
@@ -4818,10 +4449,8 @@ application_operator(Node) ->
%% =====================================================================
-%% @spec application_arguments(syntaxTree()) -> [syntaxTree()]
-%%
%% @doc Returns the list of argument subtrees of an
-%% <code>application</code> node.
+%% `application' node.
%%
%% @see application/2
@@ -4837,11 +4466,8 @@ application_arguments(Node) ->
%% =====================================================================
-%% @spec list_comp(Template::syntaxTree(), Body::[syntaxTree()]) ->
-%% syntaxTree()
-%%
-%% @doc Creates an abstract list comprehension. If <code>Body</code> is
-%% <code>[E1, ..., En]</code>, the result represents
+%% @doc Creates an abstract list comprehension. If `Body' is
+%% `[E1, ..., En]', the result represents
%% "<code>[<em>Template</em> || <em>E1</em>, ..., <em>En</em>]</code>".
%%
%% @see list_comp_template/1
@@ -4876,9 +4502,7 @@ revert_list_comp(Node) ->
%% =====================================================================
-%% @spec list_comp_template(syntaxTree()) -> syntaxTree()
-%%
-%% @doc Returns the template subtree of a <code>list_comp</code> node.
+%% @doc Returns the template subtree of a `list_comp' node.
%%
%% @see list_comp/2
@@ -4894,10 +4518,7 @@ list_comp_template(Node) ->
%% =====================================================================
-%% @spec list_comp_body(syntaxTree()) -> [syntaxTree()]
-%%
-%% @doc Returns the list of body subtrees of a <code>list_comp</code>
-%% node.
+%% @doc Returns the list of body subtrees of a `list_comp' node.
%%
%% @see list_comp/2
@@ -4912,11 +4533,8 @@ list_comp_body(Node) ->
end.
%% =====================================================================
-%% @spec binary_comp(Template::syntaxTree(), Body::[syntaxTree()]) ->
-%% syntaxTree()
-%%
-%% @doc Creates an abstract binary comprehension. If <code>Body</code> is
-%% <code>[E1, ..., En]</code>, the result represents
+%% @doc Creates an abstract binary comprehension. If `Body' is
+%% `[E1, ..., En]', the result represents
%% "<code>&lt;&lt;<em>Template</em> || <em>E1</em>, ..., <em>En</em>&gt;&gt;</code>".
%%
%% @see binary_comp_template/1
@@ -4951,9 +4569,7 @@ revert_binary_comp(Node) ->
%% =====================================================================
-%% @spec binary_comp_template(syntaxTree()) -> syntaxTree()
-%%
-%% @doc Returns the template subtree of a <code>binary_comp</code> node.
+%% @doc Returns the template subtree of a `binary_comp' node.
%%
%% @see binary_comp/2
@@ -4969,10 +4585,7 @@ binary_comp_template(Node) ->
%% =====================================================================
-%% @spec binary_comp_body(syntaxTree()) -> [syntaxTree()]
-%%
-%% @doc Returns the list of body subtrees of a <code>binary_comp</code>
-%% node.
+%% @doc Returns the list of body subtrees of a `binary_comp' node.
%%
%% @see binary_comp/2
@@ -4988,8 +4601,6 @@ binary_comp_body(Node) ->
%% =====================================================================
-%% @spec query_expr(Body::syntaxTree()) -> syntaxTree()
-%%
%% @doc Creates an abstract Mnemosyne query expression. The result
%% represents "<code>query <em>Body</em> end</code>".
%%
@@ -5018,9 +4629,7 @@ revert_query_expr(Node) ->
%% =====================================================================
-%% @spec query_expr_body(syntaxTree()) -> syntaxTree()
-%%
-%% @doc Returns the body subtree of a <code>query_expr</code> node.
+%% @doc Returns the body subtree of a `query_expr' node.
%%
%% @see query_expr/1
@@ -5036,13 +4645,10 @@ query_expr_body(Node) ->
%% =====================================================================
-%% @spec rule(Name::syntaxTree(), Clauses::[syntaxTree()]) ->
-%% syntaxTree()
-%%
-%% @doc Creates an abstract Mnemosyne rule. If <code>Clauses</code> is
-%% <code>[C1, ..., Cn]</code>, the results represents
+%% @doc Creates an abstract Mnemosyne rule. If `Clauses' is
+%% `[C1, ..., Cn]', the results represents
%% "<code><em>Name</em> <em>C1</em>; ...; <em>Name</em>
-%% <em>Cn</em>.</code>". More exactly, if each <code>Ci</code>
+%% <em>Cn</em>.</code>". More exactly, if each `Ci'
%% represents "<code>(<em>Pi1</em>, ..., <em>Pim</em>) <em>Gi</em> ->
%% <em>Bi</em></code>", then the result represents
%% "<code><em>Name</em>(<em>P11</em>, ..., <em>P1m</em>) <em>G1</em> :-
@@ -5097,9 +4703,7 @@ revert_rule(Node) ->
%% =====================================================================
-%% @spec rule_name(syntaxTree()) -> syntaxTree()
-%%
-%% @doc Returns the name subtree of a <code>rule</code> node.
+%% @doc Returns the name subtree of a `rule' node.
%%
%% @see rule/2
@@ -5114,9 +4718,7 @@ rule_name(Node) ->
end.
%% =====================================================================
-%% @spec rule_clauses(syntaxTree()) -> [syntaxTree()]
-%%
-%% @doc Returns the list of clause subtrees of a <code>rule</code> node.
+%% @doc Returns the list of clause subtrees of a `rule' node.
%%
%% @see rule/2
@@ -5131,16 +4733,14 @@ rule_clauses(Node) ->
end.
%% =====================================================================
-%% @spec rule_arity(Node::syntaxTree()) -> integer()
-%%
-%% @doc Returns the arity of a <code>rule</code> node. The result is the
+%% @doc Returns the arity of a `rule' node. The result is the
%% number of parameter patterns in the first clause of the rule;
%% subsequent clauses are ignored.
%%
-%% <p>An exception is thrown if <code>rule_clauses(Node)</code> returns
+%% An exception is thrown if `rule_clauses(Node)' returns
%% an empty list, or if the first element of that list is not a syntax
-%% tree <code>C</code> of type <code>clause</code> such that
-%% <code>clause_patterns(C)</code> is a nonempty list.</p>
+%% tree `C' of type `clause' such that
+%% `clause_patterns(C)' is a nonempty list.
%%
%% @see rule/2
%% @see rule_clauses/1
@@ -5156,9 +4756,6 @@ rule_arity(Node) ->
%% =====================================================================
-%% @spec generator(Pattern::syntaxTree(), Body::syntaxTree()) ->
-%% syntaxTree()
-%%
%% @doc Creates an abstract generator. The result represents
%% "<code><em>Pattern</em> &lt;- <em>Body</em></code>".
%%
@@ -5193,9 +4790,7 @@ revert_generator(Node) ->
%% =====================================================================
-%% @spec generator_pattern(syntaxTree()) -> syntaxTree()
-%%
-%% @doc Returns the pattern subtree of a <code>generator</code> node.
+%% @doc Returns the pattern subtree of a `generator' node.
%%
%% @see generator/2
@@ -5211,9 +4806,7 @@ generator_pattern(Node) ->
%% =====================================================================
-%% @spec generator_body(syntaxTree()) -> syntaxTree()
-%%
-%% @doc Returns the body subtree of a <code>generator</code> node.
+%% @doc Returns the body subtree of a `generator' node.
%%
%% @see generator/2
@@ -5229,9 +4822,6 @@ generator_body(Node) ->
%% =====================================================================
-%% @spec binary_generator(Pattern::syntaxTree(), Body::syntaxTree()) ->
-%% syntaxTree()
-%%
%% @doc Creates an abstract binary_generator. The result represents
%% "<code><em>Pattern</em> &lt;- <em>Body</em></code>".
%%
@@ -5266,9 +4856,7 @@ revert_binary_generator(Node) ->
%% =====================================================================
-%% @spec binary_generator_pattern(syntaxTree()) -> syntaxTree()
-%%
-%% @doc Returns the pattern subtree of a <code>generator</code> node.
+%% @doc Returns the pattern subtree of a `generator' node.
%%
%% @see binary_generator/2
@@ -5284,9 +4872,7 @@ binary_generator_pattern(Node) ->
%% =====================================================================
-%% @spec binary_generator_body(syntaxTree()) -> syntaxTree()
-%%
-%% @doc Returns the body subtree of a <code>generator</code> node.
+%% @doc Returns the body subtree of a `generator' node.
%%
%% @see binary_generator/2
@@ -5302,10 +4888,8 @@ binary_generator_body(Node) ->
%% =====================================================================
-%% @spec block_expr(Body::[syntaxTree()]) -> syntaxTree()
-%%
-%% @doc Creates an abstract block expression. If <code>Body</code> is
-%% <code>[B1, ..., Bn]</code>, the result represents "<code>begin
+%% @doc Creates an abstract block expression. If `Body' is
+%% `[B1, ..., Bn]', the result represents "<code>begin
%% <em>B1</em>, ..., <em>Bn</em> end</code>".
%%
%% @see block_expr_body/1
@@ -5321,7 +4905,7 @@ binary_generator_body(Node) ->
%%
%% Body = [erl_parse()] \ []
--spec block_expr(Body::[syntaxTree()]) -> syntaxTree().
+-spec block_expr([syntaxTree()]) -> syntaxTree().
block_expr(Body) ->
tree(block_expr, Body).
@@ -5333,10 +4917,7 @@ revert_block_expr(Node) ->
%% =====================================================================
-%% @spec block_expr_body(syntaxTree()) -> [syntaxTree()]
-%%
-%% @doc Returns the list of body subtrees of a <code>block_expr</code>
-%% node.
+%% @doc Returns the list of body subtrees of a `block_expr' node.
%%
%% @see block_expr/1
@@ -5352,12 +4933,10 @@ block_expr_body(Node) ->
%% =====================================================================
-%% @spec if_expr(Clauses::[syntaxTree()]) -> syntaxTree()
-%%
-%% @doc Creates an abstract if-expression. If <code>Clauses</code> is
-%% <code>[C1, ..., Cn]</code>, the result represents "<code>if
+%% @doc Creates an abstract if-expression. If `Clauses' is
+%% `[C1, ..., Cn]', the result represents "<code>if
%% <em>C1</em>; ...; <em>Cn</em> end</code>". More exactly, if each
-%% <code>Ci</code> represents "<code>() <em>Gi</em> ->
+%% `Ci' represents "<code>() <em>Gi</em> ->
%% <em>Bi</em></code>", then the result represents "<code>if
%% <em>G1</em> -> <em>B1</em>; ...; <em>Gn</em> -> <em>Bn</em>
%% end</code>".
@@ -5392,10 +4971,7 @@ revert_if_expr(Node) ->
%% =====================================================================
-%% @spec if_expr_clauses(syntaxTree()) -> [syntaxTree()]
-%%
-%% @doc Returns the list of clause subtrees of an <code>if_expr</code>
-%% node.
+%% @doc Returns the list of clause subtrees of an `if_expr' node.
%%
%% @see if_expr/1
@@ -5411,13 +4987,10 @@ if_expr_clauses(Node) ->
%% =====================================================================
-%% @spec case_expr(Argument::syntaxTree(), Clauses::[syntaxTree()]) ->
-%% syntaxTree()
-%%
-%% @doc Creates an abstract case-expression. If <code>Clauses</code> is
-%% <code>[C1, ..., Cn]</code>, the result represents "<code>case
+%% @doc Creates an abstract case-expression. If `Clauses' is
+%% `[C1, ..., Cn]', the result represents "<code>case
%% <em>Argument</em> of <em>C1</em>; ...; <em>Cn</em> end</code>". More
-%% exactly, if each <code>Ci</code> represents "<code>(<em>Pi</em>)
+%% exactly, if each `Ci' represents "<code>(<em>Pi</em>)
%% <em>Gi</em> -> <em>Bi</em></code>", then the result represents
%% "<code>case <em>Argument</em> of <em>P1</em> <em>G1</em> ->
%% <em>B1</em>; ...; <em>Pn</em> <em>Gn</em> -> <em>Bn</em> end</code>".
@@ -5461,9 +5034,7 @@ revert_case_expr(Node) ->
%% =====================================================================
-%% @spec case_expr_argument(syntaxTree()) -> syntaxTree()
-%%
-%% @doc Returns the argument subtree of a <code>case_expr</code> node.
+%% @doc Returns the argument subtree of a `case_expr' node.
%%
%% @see case_expr/2
@@ -5479,10 +5050,7 @@ case_expr_argument(Node) ->
%% =====================================================================
-%% @spec case_expr_clauses(syntaxTree()) -> [syntaxTree()]
-%%
-%% @doc Returns the list of clause subtrees of a <code>case_expr</code>
-%% node.
+%% @doc Returns the list of clause subtrees of a `case_expr' node.
%%
%% @see case_expr/2
@@ -5498,12 +5066,10 @@ case_expr_clauses(Node) ->
%% =====================================================================
-%% @spec cond_expr(Clauses::[syntaxTree()]) -> syntaxTree()
-%%
-%% @doc Creates an abstract cond-expression. If <code>Clauses</code> is
-%% <code>[C1, ..., Cn]</code>, the result represents "<code>cond
+%% @doc Creates an abstract cond-expression. If `Clauses' is
+%% `[C1, ..., Cn]', the result represents "<code>cond
%% <em>C1</em>; ...; <em>Cn</em> end</code>". More exactly, if each
-%% <code>Ci</code> represents "<code>() <em>Ei</em> ->
+%% `Ci' represents "<code>() <em>Ei</em> ->
%% <em>Bi</em></code>", then the result represents "<code>cond
%% <em>E1</em> -> <em>B1</em>; ...; <em>En</em> -> <em>Bn</em>
%% end</code>".
@@ -5538,10 +5104,7 @@ revert_cond_expr(Node) ->
%% =====================================================================
-%% @spec cond_expr_clauses(syntaxTree()) -> [syntaxTree()]
-%%
-%% @doc Returns the list of clause subtrees of a <code>cond_expr</code>
-%% node.
+%% @doc Returns the list of clause subtrees of a `cond_expr' node.
%%
%% @see cond_expr/1
@@ -5557,7 +5120,6 @@ cond_expr_clauses(Node) ->
%% =====================================================================
-%% @spec receive_expr(Clauses) -> syntaxTree()
%% @equiv receive_expr(Clauses, none, [])
-spec receive_expr([syntaxTree()]) -> syntaxTree().
@@ -5567,25 +5129,21 @@ receive_expr(Clauses) ->
%% =====================================================================
-%% @spec receive_expr(Clauses::[syntaxTree()], Timeout,
-%% Action::[syntaxTree()]) -> syntaxTree()
-%% Timeout = none | syntaxTree()
-%%
-%% @doc Creates an abstract receive-expression. If <code>Timeout</code>
-%% is <code>none</code>, the result represents "<code>receive
-%% <em>C1</em>; ...; <em>Cn</em> end</code>" (the <code>Action</code>
-%% argument is ignored). Otherwise, if <code>Clauses</code> is
-%% <code>[C1, ..., Cn]</code> and <code>Action</code> is <code>[A1, ...,
-%% Am]</code>, the result represents "<code>receive <em>C1</em>; ...;
+%% @doc Creates an abstract receive-expression. If `Timeout'
+%% is `none', the result represents "<code>receive
+%% <em>C1</em>; ...; <em>Cn</em> end</code>" (the `Action'
+%% argument is ignored). Otherwise, if `Clauses' is
+%% `[C1, ..., Cn]' and `Action' is `[A1, ...,
+%% Am]', the result represents "<code>receive <em>C1</em>; ...;
%% <em>Cn</em> after <em>Timeout</em> -> <em>A1</em>, ..., <em>Am</em>
-%% end</code>". More exactly, if each <code>Ci</code> represents
+%% end</code>". More exactly, if each `Ci' represents
%% "<code>(<em>Pi</em>) <em>Gi</em> -> <em>Bi</em></code>", then the
%% result represents "<code>receive <em>P1</em> <em>G1</em> ->
%% <em>B1</em>; ...; <em>Pn</em> <em>Gn</em> -> <em>Bn</em> ...
%% end</code>".
%%
-%% <p>Note that in Erlang, a receive-expression must have at least one
-%% clause if no timeout part is specified.</p>
+%% Note that in Erlang, a receive-expression must have at least one
+%% clause if no timeout part is specified.
%%
%% @see receive_expr_clauses/1
%% @see receive_expr_timeout/1
@@ -5649,11 +5207,8 @@ revert_receive_expr(Node) ->
%% =====================================================================
-%% @spec receive_expr_clauses(syntaxTree()) -> [syntaxTree()]
-%% type(Node) = receive_expr
-%%
%% @doc Returns the list of clause subtrees of a
-%% <code>receive_expr</code> node.
+%% `receive_expr' node.
%%
%% @see receive_expr/3
@@ -5671,15 +5226,12 @@ receive_expr_clauses(Node) ->
%% =====================================================================
-%% @spec receive_expr_timeout(Node::syntaxTree()) -> Timeout
-%% Timeout = none | syntaxTree()
-%%
-%% @doc Returns the timeout subtree of a <code>receive_expr</code> node,
-%% if any. If <code>Node</code> represents "<code>receive <em>C1</em>;
-%% ...; <em>Cn</em> end</code>", <code>none</code> is returned.
-%% Otherwise, if <code>Node</code> represents "<code>receive
+%% @doc Returns the timeout subtree of a `receive_expr' node,
+%% if any. If `Node' represents "<code>receive <em>C1</em>;
+%% ...; <em>Cn</em> end</code>", `none' is returned.
+%% Otherwise, if `Node' represents "<code>receive
%% <em>C1</em>; ...; <em>Cn</em> after <em>Timeout</em> -> ... end</code>",
-%% <code>Timeout</code> is returned.
+%% `Timeout' is returned.
%%
%% @see receive_expr/3
@@ -5697,10 +5249,8 @@ receive_expr_timeout(Node) ->
%% =====================================================================
-%% @spec receive_expr_action(Node::syntaxTree()) -> [syntaxTree()]
-%%
%% @doc Returns the list of action body subtrees of a
-%% <code>receive_expr</code> node. If <code>Node</code> represents
+%% `receive_expr' node. If `Node' represents
%% "<code>receive <em>C1</em>; ...; <em>Cn</em> end</code>", this is the
%% empty list.
%%
@@ -5720,8 +5270,6 @@ receive_expr_action(Node) ->
%% =====================================================================
-%% @spec try_expr(Body::syntaxTree(), Handlers::[syntaxTree()]) ->
-%% syntaxTree()
%% @equiv try_expr(Body, [], Handlers)
-spec try_expr([syntaxTree()], [syntaxTree()]) -> syntaxTree().
@@ -5731,8 +5279,6 @@ try_expr(Body, Handlers) ->
%% =====================================================================
-%% @spec try_expr(Body::syntaxTree(), Clauses::[syntaxTree()],
-%% Handlers::[syntaxTree()]) -> syntaxTree()
%% @equiv try_expr(Body, Clauses, Handlers, [])
-spec try_expr([syntaxTree()], [syntaxTree()], [syntaxTree()]) -> syntaxTree().
@@ -5742,8 +5288,6 @@ try_expr(Body, Clauses, Handlers) ->
%% =====================================================================
-%% @spec try_after_expr(Body::syntaxTree(), After::[syntaxTree()]) ->
-%% syntaxTree()
%% @equiv try_expr(Body, [], [], After)
-spec try_after_expr([syntaxTree()], [syntaxTree()]) -> syntaxTree().
@@ -5753,30 +5297,26 @@ try_after_expr(Body, After) ->
%% =====================================================================
-%% @spec try_expr(Body::[syntaxTree()], Clauses::[syntaxTree()],
-%% Handlers::[syntaxTree()], After::[syntaxTree()]) ->
-%% syntaxTree()
-%%
-%% @doc Creates an abstract try-expression. If <code>Body</code> is
-%% <code>[B1, ..., Bn]</code>, <code>Clauses</code> is <code>[C1, ...,
-%% Cj]</code>, <code>Handlers</code> is <code>[H1, ..., Hk]</code>, and
-%% <code>After</code> is <code>[A1, ..., Am]</code>, the result
+%% @doc Creates an abstract try-expression. If `Body' is
+%% `[B1, ..., Bn]', `Clauses' is `[C1, ...,
+%% Cj]', `Handlers' is `[H1, ..., Hk]', and
+%% `After' is `[A1, ..., Am]', the result
%% represents "<code>try <em>B1</em>, ..., <em>Bn</em> of <em>C1</em>;
%% ...; <em>Cj</em> catch <em>H1</em>; ...; <em>Hk</em> after
%% <em>A1</em>, ..., <em>Am</em> end</code>". More exactly, if each
-%% <code>Ci</code> represents "<code>(<em>CPi</em>) <em>CGi</em> ->
-%% <em>CBi</em></code>", and each <code>Hi</code> represents
+%% `Ci' represents "<code>(<em>CPi</em>) <em>CGi</em> ->
+%% <em>CBi</em></code>", and each `Hi' represents
%% "<code>(<em>HPi</em>) <em>HGi</em> -> <em>HBi</em></code>", then the
%% result represents "<code>try <em>B1</em>, ..., <em>Bn</em> of
%% <em>CP1</em> <em>CG1</em> -> <em>CB1</em>; ...; <em>CPj</em>
%% <em>CGj</em> -> <em>CBj</em> catch <em>HP1</em> <em>HG1</em> ->
%% <em>HB1</em>; ...; <em>HPk</em> <em>HGk</em> -> <em>HBk</em> after
-%% <em>A1</em>, ..., <em>Am</em> end</code>"; cf.
-%% <code>case_expr/2</code>. If <code>Clauses</code> is the empty list,
-%% the <code>of ...</code> section is left out. If <code>After</code> is
-%% the empty list, the <code>after ...</code> section is left out. If
-%% <code>Handlers</code> is the empty list, and <code>After</code> is
-%% nonempty, the <code>catch ...</code> section is left out.
+%% <em>A1</em>, ..., <em>Am</em> end</code>"; see
+%% {@link case_expr/2}. If `Clauses' is the empty list,
+%% the `of ...' section is left out. If `After' is
+%% the empty list, the `after ...' section is left out. If
+%% `Handlers' is the empty list, and `After' is
+%% nonempty, the `catch ...' section is left out.
%%
%% @see try_expr_body/1
%% @see try_expr_clauses/1
@@ -5834,10 +5374,7 @@ revert_try_expr(Node) ->
%% =====================================================================
-%% @spec try_expr_body(syntaxTree()) -> [syntaxTree()]
-%%
-%% @doc Returns the list of body subtrees of a <code>try_expr</code>
-%% node.
+%% @doc Returns the list of body subtrees of a `try_expr' node.
%%
%% @see try_expr/4
@@ -5853,10 +5390,8 @@ try_expr_body(Node) ->
%% =====================================================================
-%% @spec try_expr_clauses(Node::syntaxTree()) -> [syntaxTree()]
-%%
%% @doc Returns the list of case-clause subtrees of a
-%% <code>try_expr</code> node. If <code>Node</code> represents
+%% `try_expr' node. If `Node' represents
%% "<code>try <em>Body</em> catch <em>H1</em>; ...; <em>Hn</em>
%% end</code>", the result is the empty list.
%%
@@ -5874,10 +5409,8 @@ try_expr_clauses(Node) ->
%% =====================================================================
-%% @spec try_expr_handlers(syntaxTree()) -> [syntaxTree()]
-%%
%% @doc Returns the list of handler-clause subtrees of a
-%% <code>try_expr</code> node.
+%% `try_expr' node.
%%
%% @see try_expr/4
@@ -5893,10 +5426,7 @@ try_expr_handlers(Node) ->
%% =====================================================================
-%% @spec try_expr_after(syntaxTree()) -> [syntaxTree()]
-%%
-%% @doc Returns the list of "after" subtrees of a <code>try_expr</code>
-%% node.
+%% @doc Returns the list of "after" subtrees of a `try_expr' node.
%%
%% @see try_expr/4
@@ -5912,9 +5442,6 @@ try_expr_after(Node) ->
%% =====================================================================
-%% @spec class_qualifier(Class::syntaxTree(), Body::syntaxTree()) ->
-%% syntaxTree()
-%%
%% @doc Creates an abstract class qualifier. The result represents
%% "<code><em>Class</em>:<em>Body</em></code>".
%%
@@ -5937,10 +5464,8 @@ class_qualifier(Class, Body) ->
%% =====================================================================
-%% @spec class_qualifier_argument(syntaxTree()) -> syntaxTree()
-%%
%% @doc Returns the argument (the class) subtree of a
-%% <code>class_qualifier</code> node.
+%% `class_qualifier' node.
%%
%% @see class_qualifier/2
@@ -5951,9 +5476,7 @@ class_qualifier_argument(Node) ->
%% =====================================================================
-%% @spec class_qualifier_body(syntaxTree()) -> syntaxTree()
-%%
-%% @doc Returns the body subtree of a <code>class_qualifier</code> node.
+%% @doc Returns the body subtree of a `class_qualifier' node.
%%
%% @see class_qualifier/2
@@ -5964,13 +5487,10 @@ class_qualifier_body(Node) ->
%% =====================================================================
-%% @spec implicit_fun(Name::syntaxTree(), Arity::syntaxTree()) ->
-%% syntaxTree()
-%%
%% @doc Creates an abstract "implicit fun" expression. If
-%% <code>Arity</code> is <code>none</code>, this is equivalent to
-%% <code>implicit_fun(Name)</code>, otherwise it is equivalent to
-%% <code>implicit_fun(arity_qualifier(Name, Arity))</code>.
+%% `Arity' is `none', this is equivalent to
+%% `implicit_fun(Name)', otherwise it is equivalent to
+%% `implicit_fun(arity_qualifier(Name, Arity))'.
%%
%% (This is a utility function.)
%%
@@ -5986,14 +5506,11 @@ implicit_fun(Name, Arity) ->
%% =====================================================================
-%% @spec implicit_fun(Module::syntaxTree(), Name::syntaxTree(),
-%% Arity::syntaxTree()) -> syntaxTree()
-%%
%% @doc Creates an abstract module-qualified "implicit fun" expression.
-%% If <code>Module</code> is <code>none</code>, this is equivalent to
-%% <code>implicit_fun(Name, Arity)</code>, otherwise it is equivalent to
-%% <code>implicit_fun(module_qualifier(Module, arity_qualifier(Name,
-%% Arity))</code>.
+%% If `Module' is `none', this is equivalent to
+%% `implicit_fun(Name, Arity)', otherwise it is equivalent to
+%% `implicit_fun(module_qualifier(Module, arity_qualifier(Name,
+%% Arity))'.
%%
%% (This is a utility function.)
%%
@@ -6010,10 +5527,8 @@ implicit_fun(Module, Name, Arity) ->
%% =====================================================================
-%% @spec implicit_fun(Name::syntaxTree()) -> syntaxTree()
-%%
%% @doc Creates an abstract "implicit fun" expression. The result
-%% represents "<code>fun <em>Name</em></code>". <code>Name</code> should
+%% represents "<code>fun <em>Name</em></code>". `Name' should
%% represent either <code><em>F</em>/<em>A</em></code> or
%% <code><em>M</em>:<em>F</em>/<em>A</em></code>
%%
@@ -6072,15 +5587,13 @@ revert_implicit_fun(Node) ->
%% =====================================================================
-%% @spec implicit_fun_name(Node::syntaxTree()) -> syntaxTree()
+%% @doc Returns the name subtree of an `implicit_fun' node.
%%
-%% @doc Returns the name subtree of an <code>implicit_fun</code> node.
-%%
-%% <p>Note: if <code>Node</code> represents "<code>fun
+%% Note: if `Node' represents "<code>fun
%% <em>N</em>/<em>A</em></code>" or "<code>fun
%% <em>M</em>:<em>N</em>/<em>A</em></code>", then the result is the
%% subtree representing "<code><em>N</em>/<em>A</em></code>" or
-%% "<code><em>M</em>:<em>N</em>/<em>A</em></code>", respectively.</p>
+%% "<code><em>M</em>:<em>N</em>/<em>A</em></code>", respectively.
%%
%% @see implicit_fun/1
%% @see arity_qualifier/2
@@ -6110,12 +5623,10 @@ implicit_fun_name(Node) ->
%% =====================================================================
-%% @spec fun_expr(Clauses::[syntaxTree()]) -> syntaxTree()
-%%
-%% @doc Creates an abstract fun-expression. If <code>Clauses</code> is
-%% <code>[C1, ..., Cn]</code>, the result represents "<code>fun
+%% @doc Creates an abstract fun-expression. If `Clauses' is
+%% `[C1, ..., Cn]', the result represents "<code>fun
%% <em>C1</em>; ...; <em>Cn</em> end</code>". More exactly, if each
-%% <code>Ci</code> represents "<code>(<em>Pi1</em>, ..., <em>Pim</em>)
+%% `Ci' represents "<code>(<em>Pi1</em>, ..., <em>Pim</em>)
%% <em>Gi</em> -> <em>Bi</em></code>", then the result represents
%% "<code>fun (<em>P11</em>, ..., <em>P1m</em>) <em>G1</em> ->
%% <em>B1</em>; ...; (<em>Pn1</em>, ..., <em>Pnm</em>) <em>Gn</em> ->
@@ -6152,10 +5663,7 @@ revert_fun_expr(Node) ->
%% =====================================================================
-%% @spec fun_expr_clauses(syntaxTree()) -> [syntaxTree()]
-%%
-%% @doc Returns the list of clause subtrees of a <code>fun_expr</code>
-%% node.
+%% @doc Returns the list of clause subtrees of a `fun_expr' node.
%%
%% @see fun_expr/1
@@ -6171,16 +5679,14 @@ fun_expr_clauses(Node) ->
%% =====================================================================
-%% @spec fun_expr_arity(syntaxTree()) -> integer()
-%%
-%% @doc Returns the arity of a <code>fun_expr</code> node. The result is
+%% @doc Returns the arity of a `fun_expr' node. The result is
%% the number of parameter patterns in the first clause of the
%% fun-expression; subsequent clauses are ignored.
%%
-%% <p>An exception is thrown if <code>fun_expr_clauses(Node)</code>
+%% An exception is thrown if `fun_expr_clauses(Node)'
%% returns an empty list, or if the first element of that list is not a
-%% syntax tree <code>C</code> of type <code>clause</code> such that
-%% <code>clause_patterns(C)</code> is a nonempty list.</p>
+%% syntax tree `C' of type `clause' such that
+%% `clause_patterns(C)' is a nonempty list.
%%
%% @see fun_expr/1
%% @see fun_expr_clauses/1
@@ -6194,8 +5700,6 @@ fun_expr_arity(Node) ->
%% =====================================================================
-%% @spec parentheses(Body::syntaxTree()) -> syntaxTree()
-%%
%% @doc Creates an abstract parenthesised expression. The result
%% represents "<code>(<em>Body</em>)</code>", independently of the
%% context.
@@ -6215,9 +5719,7 @@ revert_parentheses(Node) ->
%% =====================================================================
-%% @spec parentheses_body(syntaxTree()) -> syntaxTree()
-%%
-%% @doc Returns the body subtree of a <code>parentheses</code> node.
+%% @doc Returns the body subtree of a `parentheses' node.
%%
%% @see parentheses/1
@@ -6228,7 +5730,6 @@ parentheses_body(Node) ->
%% =====================================================================
-%% @spec macro(Name) -> syntaxTree()
%% @equiv macro(Name, none)
-spec macro(syntaxTree()) -> syntaxTree().
@@ -6238,25 +5739,22 @@ macro(Name) ->
%% =====================================================================
-%% @spec macro(Name::syntaxTree(), Arguments) -> syntaxTree()
-%% Arguments = none | [syntaxTree()]
-%%
-%% @doc Creates an abstract macro application. If <code>Arguments</code>
-%% is <code>none</code>, the result represents
-%% "<code>?<em>Name</em></code>", otherwise, if <code>Arguments</code>
-%% is <code>[A1, ..., An]</code>, the result represents
+%% @doc Creates an abstract macro application. If `Arguments'
+%% is `none', the result represents
+%% "<code>?<em>Name</em></code>", otherwise, if `Arguments'
+%% is `[A1, ..., An]', the result represents
%% "<code>?<em>Name</em>(<em>A1</em>, ..., <em>An</em>)</code>".
%%
-%% <p>Notes: if <code>Arguments</code> is the empty list, the result
+%% Notes: if `Arguments' is the empty list, the result
%% will thus represent "<code>?<em>Name</em>()</code>", including a pair
-%% of matching parentheses.</p>
+%% of matching parentheses.
%%
-%% <p>The only syntactical limitation imposed by the preprocessor on the
+%% The only syntactical limitation imposed by the preprocessor on the
%% arguments to a macro application (viewed as sequences of tokens) is
%% that they must be balanced with respect to parentheses, brackets,
-%% <code>begin ... end</code>, <code>case ... end</code>, etc. The
-%% <code>text</code> node type can be used to represent arguments which
-%% are not regular Erlang constructs.</p>
+%% `begin ... end', `case ... end', etc. The
+%% `text' node type can be used to represent arguments which
+%% are not regular Erlang constructs.
%%
%% @see macro_name/1
%% @see macro_arguments/1
@@ -6278,9 +5776,7 @@ macro(Name, Arguments) ->
%% =====================================================================
-%% @spec macro_name(syntaxTree()) -> syntaxTree()
-%%
-%% @doc Returns the name subtree of a <code>macro</code> node.
+%% @doc Returns the name subtree of a `macro' node.
%%
%% @see macro/2
@@ -6291,14 +5787,12 @@ macro_name(Node) ->
%% =====================================================================
-%% @spec macro_arguments(Node::syntaxTree()) -> none | [syntaxTree()]
-%%
-%% @doc Returns the list of argument subtrees of a <code>macro</code>
-%% node, if any. If <code>Node</code> represents
-%% "<code>?<em>Name</em></code>", <code>none</code> is returned.
-%% Otherwise, if <code>Node</code> represents
+%% @doc Returns the list of argument subtrees of a `macro'
+%% node, if any. If `Node' represents
+%% "<code>?<em>Name</em></code>", `none' is returned.
+%% Otherwise, if `Node' represents
%% "<code>?<em>Name</em>(<em>A1</em>, ..., <em>An</em>)</code>",
-%% <code>[A1, ..., An]</code> is returned.
+%% `[A1, ..., An]' is returned.
%%
%% @see macro/2
@@ -6309,15 +5803,13 @@ macro_arguments(Node) ->
%% =====================================================================
-%% @spec abstract(Term::term()) -> syntaxTree()
-%%
%% @doc Returns the syntax tree corresponding to an Erlang term.
-%% <code>Term</code> must be a literal term, i.e., one that can be
+%% `Term' must be a literal term, i.e., one that can be
%% represented as a source code literal. Thus, it may not contain a
%% process identifier, port, reference, binary or function value as a
%% subterm. The function recognises printable strings, in order to get a
%% compact and readable representation. Evaluation fails with reason
-%% <code>badarg</code> if <code>Term</code> is not a literal term.
+%% `badarg' if `Term' is not a literal term.
%%
%% @see concrete/1
%% @see is_literal/1
@@ -6367,19 +5859,17 @@ abstract_tail(H, T) ->
%% =====================================================================
-%% @spec concrete(Node::syntaxTree()) -> term()
-%%
%% @doc Returns the Erlang term represented by a syntax tree. Evaluation
-%% fails with reason <code>badarg</code> if <code>Node</code> does not
+%% fails with reason `badarg' if `Node' does not
%% represent a literal term.
%%
-%% <p>Note: Currently, the set of syntax trees which have a concrete
+%% Note: Currently, the set of syntax trees which have a concrete
%% representation is larger than the set of trees which can be built
-%% using the function <code>abstract/1</code>. An abstract character
-%% will be concretised as an integer, while <code>abstract/1</code> does
+%% using the function {@link abstract/1}. An abstract character
+%% will be concretised as an integer, while {@link abstract/1} does
%% not at present yield an abstract character for any input. (Use the
-%% <code>char/1</code> function to explicitly create an abstract
-%% character.)</p>
+%% {@link char/1} function to explicitly create an abstract
+%% character.)
%%
%% @see abstract/1
%% @see is_literal/1
@@ -6422,7 +5912,7 @@ concrete(Node) ->
{value, concrete(F), []}
end, [], true),
B;
- _ ->
+ _ ->
erlang:error({badarg, Node})
end.
@@ -6433,12 +5923,10 @@ concrete_list([]) ->
%% =====================================================================
-%% @spec is_literal(Node::syntaxTree()) -> boolean()
-%%
-%% @doc Returns <code>true</code> if <code>Node</code> represents a
-%% literal term, otherwise <code>false</code>. This function returns
-%% <code>true</code> if and only if the value of
-%% <code>concrete(Node)</code> is defined.
+%% @doc Returns `true' if `Node' represents a
+%% literal term, otherwise `false'. This function returns
+%% `true' if and only if the value of
+%% `concrete(Node)' is defined.
%%
%% @see abstract/1
%% @see concrete/1
@@ -6469,21 +5957,19 @@ is_literal(T) ->
%% =====================================================================
-%% @spec revert(Tree::syntaxTree()) -> syntaxTree()
-%%
-%% @doc Returns an <code>erl_parse</code>-compatible representation of a
-%% syntax tree, if possible. If <code>Tree</code> represents a
+%% @doc Returns an `erl_parse'-compatible representation of a
+%% syntax tree, if possible. If `Tree' represents a
%% well-formed Erlang program or expression, the conversion should work
-%% without problems. Typically, <code>is_tree/1</code> yields
-%% <code>true</code> if conversion failed (i.e., the result is still an
-%% abstract syntax tree), and <code>false</code> otherwise.
+%% without problems. Typically, {@link is_tree/1} yields
+%% `true' if conversion failed (i.e., the result is still an
+%% abstract syntax tree), and `false' otherwise.
%%
-%% <p>The <code>is_tree/1</code> test is not completely foolproof. For a
-%% few special node types (e.g. <code>arity_qualifier</code>), if such a
+%% The {@link is_tree/1} test is not completely foolproof. For a
+%% few special node types (e.g. `arity_qualifier'), if such a
%% node occurs in a context where it is not expected, it will be left
%% unchanged as a non-reverted subtree of the result. This can only
-%% happen if <code>Tree</code> does not actually represent legal Erlang
-%% code.</p>
+%% happen if `Tree' does not actually represent legal Erlang
+%% code.
%%
%% @see revert_forms/1
%% @see //stdlib/erl_parse
@@ -6493,9 +5979,13 @@ is_literal(T) ->
revert(Node) ->
case is_tree(Node) of
false ->
- %% Just remove any wrapper. `erl_parse' nodes never contain
- %% abstract syntax tree nodes as subtrees.
- unwrap(Node);
+ %% Just remove any wrapper and copy the position. `erl_parse'
+ %% nodes never contain abstract syntax tree nodes as subtrees.
+ case unwrap(Node) of
+ {error, Info} -> {error, setelement(1,Info,get_pos(Node))};
+ {warning, Info} -> {warning, setelement(1,Info,get_pos(Node))};
+ Node1 -> setelement(2,Node1,get_pos(Node))
+ end;
true ->
case is_leaf(Node) of
true ->
@@ -6615,16 +6105,12 @@ revert_root(Node) ->
%% =====================================================================
-%% @spec revert_forms(Forms) -> [erl_parse()]
-%%
-%% Forms = syntaxTree() | [syntaxTree()]
-%%
%% @doc Reverts a sequence of Erlang source code forms. The sequence can
-%% be given either as a <code>form_list</code> syntax tree (possibly
+%% be given either as a `form_list' syntax tree (possibly
%% nested), or as a list of "program form" syntax trees. If successful,
-%% the corresponding flat list of <code>erl_parse</code>-compatible
-%% syntax trees is returned (cf. <code>revert/1</code>). If some program
-%% form could not be reverted, <code>{error, Form}</code> is thrown.
+%% the corresponding flat list of `erl_parse'-compatible
+%% syntax trees is returned (see {@link revert/1}). If some program
+%% form could not be reverted, `{error, Form}' is thrown.
%% Standalone comments in the form sequence are discarded.
%%
%% @see revert/1
@@ -6633,10 +6119,10 @@ revert_root(Node) ->
-type forms() :: syntaxTree() | [syntaxTree()].
-%% -spec revert_forms(forms()) -> [erl_parse()].
+-spec revert_forms(forms()) -> [erl_parse()].
-revert_forms(L) when is_list(L) ->
- revert_forms(form_list(L));
+revert_forms(Forms) when is_list(Forms) ->
+ revert_forms(form_list(Forms));
revert_forms(T) ->
case type(T) of
form_list ->
@@ -6673,60 +6159,54 @@ revert_forms_1([]) ->
%% =====================================================================
-%% @spec subtrees(Node::syntaxTree()) -> [[syntaxTree()]]
-%%
%% @doc Returns the grouped list of all subtrees of a syntax tree. If
-%% <code>Node</code> is a leaf node (cf. <code>is_leaf/1</code>), this
+%% `Node' is a leaf node (see {@link is_leaf/1}), this
%% is the empty list, otherwise the result is always a nonempty list,
-%% containing the lists of subtrees of <code>Node</code>, in
+%% containing the lists of subtrees of `Node', in
%% left-to-right order as they occur in the printed program text, and
%% grouped by category. Often, each group contains only a single
%% subtree.
%%
-%% <p>Depending on the type of <code>Node</code>, the size of some
+%% Depending on the type of `Node', the size of some
%% groups may be variable (e.g., the group consisting of all the
%% elements of a tuple), while others always contain the same number of
%% elements - usually exactly one (e.g., the group containing the
%% argument expression of a case-expression). Note, however, that the
%% exact structure of the returned list (for a given node type) should
%% in general not be depended upon, since it might be subject to change
-%% without notice.</p>
+%% without notice.
%%
-%% <p>The function <code>subtrees/1</code> and the constructor functions
-%% <code>make_tree/2</code> and <code>update_tree/2</code> can be a
+%% The function {@link subtrees/1} and the constructor functions
+%% {@link make_tree/2} and {@link update_tree/2} can be a
%% great help if one wants to traverse a syntax tree, visiting all its
%% subtrees, but treat nodes of the tree in a uniform way in most or all
%% cases. Using these functions makes this simple, and also assures that
%% your code is not overly sensitive to extensions of the syntax tree
%% data type, because any node types not explicitly handled by your code
-%% can be left to a default case.</p>
+%% can be left to a default case.
%%
-%% <p>For example:
-%% <pre>
-%% postorder(F, Tree) ->
+%% For example:
+%% ```postorder(F, Tree) ->
%% F(case subtrees(Tree) of
%% [] -> Tree;
%% List -> update_tree(Tree,
%% [[postorder(F, Subtree)
%% || Subtree &lt;- Group]
%% || Group &lt;- List])
-%% end).
-%% </pre>
-%% maps the function <code>F</code> on <code>Tree</code> and all its
+%% end).'''
+%% maps the function `F' on `Tree' and all its
%% subtrees, doing a post-order traversal of the syntax tree. (Note the
-%% use of <code>update_tree/2</code> to preserve node attributes.) For a
+%% use of {@link update_tree/2} to preserve node attributes.) For a
%% simple function like:
-%% <pre>
-%% f(Node) ->
+%% ```f(Node) ->
%% case type(Node) of
%% atom -> atom("a_" ++ atom_name(Node));
%% _ -> Node
-%% end.
-%% </pre>
-%% the call <code>postorder(fun f/1, Tree)</code> will yield a new
-%% representation of <code>Tree</code> in which all atom names have been
+%% end.'''
+%% the call `postorder(fun f/1, Tree)' will yield a new
+%% representation of `Tree' in which all atom names have been
%% extended with the prefix "a_", but nothing else (including comments,
-%% annotations and line numbers) has been changed.</p>
+%% annotations and line numbers) has been changed.
%%
%% @see make_tree/2
%% @see type/1
@@ -6896,12 +6376,9 @@ subtrees(T) ->
%% =====================================================================
-%% @spec update_tree(Node::syntaxTree(), Groups::[[syntaxTree()]]) ->
-%% syntaxTree()
-%%
%% @doc Creates a syntax tree with the same type and attributes as the
-%% given tree. This is equivalent to <code>copy_attrs(Node,
-%% make_tree(type(Node), Groups))</code>.
+%% given tree. This is equivalent to `copy_attrs(Node,
+%% make_tree(type(Node), Groups))'.
%%
%% @see make_tree/2
%% @see copy_attrs/2
@@ -6914,23 +6391,20 @@ update_tree(Node, Groups) ->
%% =====================================================================
-%% @spec make_tree(Type::atom(), Groups::[[syntaxTree()]]) ->
-%% syntaxTree()
-%%
%% @doc Creates a syntax tree with the given type and subtrees.
-%% <code>Type</code> must be a node type name (cf. <code>type/1</code>)
-%% that does not denote a leaf node type (cf. <code>is_leaf/1</code>).
-%% <code>Groups</code> must be a <em>nonempty</em> list of groups of
+%% `Type' must be a node type name (see {@link type/1})
+%% that does not denote a leaf node type (see {@link is_leaf/1}).
+%% `Groups' must be a <em>nonempty</em> list of groups of
%% syntax trees, representing the subtrees of a node of the given type,
%% in left-to-right order as they would occur in the printed program
-%% text, grouped by category as done by <code>subtrees/1</code>.
+%% text, grouped by category as done by {@link subtrees/1}.
%%
-%% <p>The result of <code>copy_attrs(Node, make_tree(type(Node),
-%% subtrees(Node)))</code> (cf. <code>update_tree/2</code>) represents
-%% the same source code text as the original <code>Node</code>, assuming
-%% that <code>subtrees(Node)</code> yields a nonempty list. However, it
+%% The result of `copy_attrs(Node, make_tree(type(Node),
+%% subtrees(Node)))' (see {@link update_tree/2}) represents
+%% the same source code text as the original `Node', assuming
+%% that `subtrees(Node)' yields a nonempty list. However, it
%% does not necessarily have the same data representation as
-%% <code>Node</code>.</p>
+%% `Node'.
%%
%% @see update_tree/2
%% @see subtrees/1
@@ -6995,42 +6469,40 @@ make_tree(tuple, [E]) -> tuple(E).
%% =====================================================================
-%% @spec meta(Tree::syntaxTree()) -> syntaxTree()
-%%
%% @doc Creates a meta-representation of a syntax tree. The result
%% represents an Erlang expression "<code><em>MetaTree</em></code>"
%% which, if evaluated, will yield a new syntax tree representing the
-%% same source code text as <code>Tree</code> (although the actual data
+%% same source code text as `Tree' (although the actual data
%% representation may be different). The expression represented by
-%% <code>MetaTree</code> is <em>implementation independent</em> with
+%% `MetaTree' is <em>implementation independent</em> with
%% regard to the data structures used by the abstract syntax tree
-%% implementation. Comments attached to nodes of <code>Tree</code> will
+%% implementation. Comments attached to nodes of `Tree' will
%% be preserved, but other attributes are lost.
%%
-%% <p>Any node in <code>Tree</code> whose node type is
-%% <code>variable</code> (cf. <code>type/1</code>), and whose list of
-%% annotations (cf. <code>get_ann/1</code>) contains the atom
-%% <code>meta_var</code>, will remain unchanged in the resulting tree,
-%% except that exactly one occurrence of <code>meta_var</code> is
-%% removed from its annotation list.</p>
+%% Any node in `Tree' whose node type is
+%% `variable' (see {@link type/1}), and whose list of
+%% annotations (see {@link get_ann/1}) contains the atom
+%% `meta_var', will remain unchanged in the resulting tree,
+%% except that exactly one occurrence of `meta_var' is
+%% removed from its annotation list.
%%
-%% <p>The main use of the function <code>meta/1</code> is to transform a
-%% data structure <code>Tree</code>, which represents a piece of program
+%% The main use of the function `meta/1' is to transform a
+%% data structure `Tree', which represents a piece of program
%% code, into a form that is <em>representation independent when
-%% printed</em>. E.g., suppose <code>Tree</code> represents a variable
-%% named "V". Then (assuming a function <code>print/1</code> for
-%% printing syntax trees), evaluating <code>print(abstract(Tree))</code>
-%% - simply using <code>abstract/1</code> to map the actual data
+%% printed</em>. E.g., suppose `Tree' represents a variable
+%% named "V". Then (assuming a function `print/1' for
+%% printing syntax trees), evaluating `print(abstract(Tree))'
+%% - simply using {@link abstract/1} to map the actual data
%% structure onto a syntax tree representation - would output a string
-%% that might look something like "<code>{tree, variable, ..., "V",
-%% ...}</code>", which is obviously dependent on the implementation of
+%% that might look something like "`{tree, variable, ..., "V",
+%% ...}'", which is obviously dependent on the implementation of
%% the abstract syntax trees. This could e.g. be useful for caching a
%% syntax tree in a file. However, in some situations like in a program
%% generator generator (with two "generator"), it may be unacceptable.
-%% Using <code>print(meta(Tree))</code> instead would output a
+%% Using `print(meta(Tree))' instead would output a
%% <em>representation independent</em> syntax tree generating
%% expression; in the above case, something like
-%% "<code>erl_syntax:variable("V")</code>".</p>
+%% "`erl_syntax:variable("V")'".
%%
%% @see abstract/1
%% @see type/1
@@ -7161,60 +6633,56 @@ meta_call(F, As) ->
%% =====================================================================
-%% @spec tree(Type) -> syntaxTree()
%% @equiv tree(Type, [])
--spec tree(atom()) -> syntaxTree().
+-spec tree(atom()) -> #tree{}.
tree(Type) ->
tree(Type, []).
%% =====================================================================
-%% @spec tree(Type::atom(), Data::term()) -> syntaxTree()
-%%
%% @doc <em>For special purposes only</em>. Creates an abstract syntax
-%% tree node with type tag <code>Type</code> and associated data
-%% <code>Data</code>.
+%% tree node with type tag `Type' and associated data
+%% `Data'.
%%
-%% <p>This function and the related <code>is_tree/1</code> and
-%% <code>data/1</code> provide a uniform way to extend the set of
-%% <code>erl_parse</code> node types. The associated data is any term,
-%% whose format may depend on the type tag.</p>
+%% This function and the related {@link is_tree/1} and
+%% {@link data/1} provide a uniform way to extend the set of
+%% `erl_parse' node types. The associated data is any term,
+%% whose format may depend on the type tag.
%%
-%% <h4>Notes:</h4>
+%% === Notes: ===
%% <ul>
%% <li>Any nodes created outside of this module must have type tags
%% distinct from those currently defined by this module; see
-%% <code>type/1</code> for a complete list.</li>
+%% {@link type/1} for a complete list.</li>
%% <li>The type tag of a syntax tree node may also be used
-%% as a primary tag by the <code>erl_parse</code> representation;
+%% as a primary tag by the `erl_parse' representation;
%% in that case, the selector functions for that node type
%% <em>must</em> handle both the abstract syntax tree and the
-%% <code>erl_parse</code> form. The function <code>type(T)</code>
+%% `erl_parse' form. The function `type(T)'
%% should return the correct type tag regardless of the
-%% representation of <code>T</code>, so that the user sees no
-%% difference between <code>erl_syntax</code> and
-%% <code>erl_parse</code> nodes.</li>
+%% representation of `T', so that the user sees no
+%% difference between `erl_syntax' and
+%% `erl_parse' nodes.</li>
%% </ul>
+%%
%% @see is_tree/1
%% @see data/1
%% @see type/1
--spec tree(atom(), term()) -> syntaxTree().
+-spec tree(atom(), term()) -> #tree{}.
tree(Type, Data) ->
#tree{type = Type, data = Data}.
%% =====================================================================
-%% @spec is_tree(Tree::syntaxTree()) -> boolean()
-%%
-%% @doc <em>For special purposes only</em>. Returns <code>true</code> if
-%% <code>Tree</code> is an abstract syntax tree and <code>false</code>
+%% @doc <em>For special purposes only</em>. Returns `true' if
+%% `Tree' is an abstract syntax tree and `false'
%% otherwise.
%%
-%% <p><em>Note</em>: this function yields <code>false</code> for all
-%% "old-style" <code>erl_parse</code>-compatible "parse trees".</p>
+%% <em>Note</em>: this function yields `false' for all
+%% "old-style" `erl_parse'-compatible "parse trees".
%%
%% @see tree/2
@@ -7227,12 +6695,10 @@ is_tree(_) ->
%% =====================================================================
-%% @spec data(Tree::syntaxTree()) -> term()
-%%
%% @doc <em>For special purposes only</em>. Returns the associated data
%% of a syntax tree node. Evaluation fails with reason
-%% <code>badarg</code> if <code>is_tree(Node)</code> does not yield
-%% <code>true</code>.
+%% `badarg' if `is_tree(Node)' does not yield
+%% `true'.
%%
%% @see tree/2
@@ -7248,26 +6714,19 @@ data(T) -> erlang:error({badarg, T}).
%% =====================================================================
-%% @spec wrap(Node::erl_parse()) -> syntaxTree()
-%%
-%% @type erl_parse() = erl_parse:parse_tree(). The "parse tree"
-%% representation built by the Erlang standard library parser
-%% <code>erl_parse</code>. This is a subset of the
-%% <a href="#type-syntaxTree"><code>syntaxTree</code></a> type.
-%%
-%% @doc Creates a wrapper structure around an <code>erl_parse</code>
+%% @doc Creates a wrapper structure around an `erl_parse'
%% "parse tree".
%%
-%% <p>This function and the related <code>unwrap/1</code> and
-%% <code>is_wrapper/1</code> provide a uniform way to attach arbitrary
-%% information to an <code>erl_parse</code> tree. Some information about
+%% This function and the related {@link unwrap/1} and
+%% {@link is_wrapper/1} provide a uniform way to attach arbitrary
+%% information to an `erl_parse' tree. Some information about
%% the encapsuled tree may be cached in the wrapper, such as the node
%% type. All functions on syntax trees must behave so that the user sees
-%% no difference between wrapped and non-wrapped <code>erl_parse</code>
+%% no difference between wrapped and non-wrapped `erl_parse'
%% trees. <em>Attaching a wrapper onto another wrapper structure is an
-%% error</em>.</p>
+%% error</em>.
-%%-spec wrap(erl_parse:parse_tree()) -> syntaxTree().
+-spec wrap(erl_parse()) -> #wrapper{}.
wrap(Node) ->
%% We assume that Node is an old-school `erl_parse' tree.
@@ -7276,24 +6735,20 @@ wrap(Node) ->
%% =====================================================================
-%% @spec unwrap(Node::syntaxTree()) -> syntaxTree()
-%%
-%% @doc Removes any wrapper structure, if present. If <code>Node</code>
+%% @doc Removes any wrapper structure, if present. If `Node'
%% is a wrapper structure, this function returns the wrapped
-%% <code>erl_parse</code> tree; otherwise it returns <code>Node</code>
+%% `erl_parse' tree; otherwise it returns `Node'
%% itself.
--spec unwrap(syntaxTree()) -> syntaxTree().
+-spec unwrap(syntaxTree()) -> #tree{} | erl_parse().
unwrap(#wrapper{tree = Node}) -> Node;
unwrap(Node) -> Node. % This could also be a new-form node.
%% =====================================================================
-%% @spec is_wrapper(Term::term()) -> boolean()
-%%
-%% @doc Returns <code>true</code> if the argument is a wrapper
-%% structure, otherwise <code>false</code>.
+%% @doc Returns `true' if the argument is a wrapper
+%% structure, otherwise `false'.
-ifndef(NO_UNUSED).
-spec is_wrapper(term()) -> boolean().
diff --git a/lib/syntax_tools/src/erl_syntax_lib.erl b/lib/syntax_tools/src/erl_syntax_lib.erl
index 97dfbfd7cd..36cd35f15d 100644
--- a/lib/syntax_tools/src/erl_syntax_lib.erl
+++ b/lib/syntax_tools/src/erl_syntax_lib.erl
@@ -14,10 +14,8 @@
%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
%% USA
%%
-%% $Id$
-%%
%% @copyright 1997-2006 Richard Carlsson
-%% @author Richard Carlsson <[email protected]>
+%% @author Richard Carlsson <[email protected]>
%% @end
%% =====================================================================
diff --git a/lib/syntax_tools/src/erl_tidy.erl b/lib/syntax_tools/src/erl_tidy.erl
index 09efc9c392..59cf6c0a92 100644
--- a/lib/syntax_tools/src/erl_tidy.erl
+++ b/lib/syntax_tools/src/erl_tidy.erl
@@ -14,10 +14,8 @@
%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
%% USA
%%
-%% $Id$
-%%
%% @copyright 1999-2006 Richard Carlsson
-%% @author Richard Carlsson <[email protected]>
+%% @author Richard Carlsson <[email protected]>
%% @end
%% =====================================================================
diff --git a/lib/syntax_tools/src/igor.erl b/lib/syntax_tools/src/igor.erl
index aa933eb54b..37e561cbbe 100644
--- a/lib/syntax_tools/src/igor.erl
+++ b/lib/syntax_tools/src/igor.erl
@@ -14,10 +14,8 @@
%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
%% USA
%%
-%% $Id$
-%%
%% @copyright 1998-2006 Richard Carlsson
-%% @author Richard Carlsson <[email protected]>
+%% @author Richard Carlsson <[email protected]>
%% @end
%% =====================================================================
diff --git a/lib/syntax_tools/src/prettypr.erl b/lib/syntax_tools/src/prettypr.erl
index c13fa30998..1b5ba6b05a 100644
--- a/lib/syntax_tools/src/prettypr.erl
+++ b/lib/syntax_tools/src/prettypr.erl
@@ -14,10 +14,8 @@
%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
%% USA
%%
-%% $Id$
-%%
%% @copyright 2000-2006 Richard Carlsson
-%% @author Richard Carlsson <[email protected]>
+%% @author Richard Carlsson <[email protected]>
%% @end
%% =====================================================================
diff --git a/lib/syntax_tools/vsn.mk b/lib/syntax_tools/vsn.mk
index 2b9a08e192..8f774c5d75 100644
--- a/lib/syntax_tools/vsn.mk
+++ b/lib/syntax_tools/vsn.mk
@@ -1 +1 @@
-SYNTAX_TOOLS_VSN = 1.6.8
+SYNTAX_TOOLS_VSN = 1.6.9
diff --git a/lib/test_server/src/test_server.erl b/lib/test_server/src/test_server.erl
index 6e94e4861a..17c5f5b253 100644
--- a/lib/test_server/src/test_server.erl
+++ b/lib/test_server/src/test_server.erl
@@ -35,6 +35,7 @@
-export([fail/0,fail/1,format/1,format/2,format/3]).
-export([capture_start/0,capture_stop/0,capture_get/0]).
-export([messages_get/0]).
+-export([permit_io/2]).
-export([hours/1,minutes/1,seconds/1,sleep/1,adjusted_sleep/1,timecall/3]).
-export([timetrap_scale_factor/0,timetrap/1,get_timetrap_info/0,
timetrap_cancel/1,timetrap_cancel/0]).
@@ -49,7 +50,7 @@
-export([run_on_shielded_node/2]).
-export([is_cover/0,is_debug/0,is_commercial/0]).
--export([break/1,continue/0]).
+-export([break/1,break/2,break/3,continue/0,continue/1]).
%%% DEBUGGER INTERFACE %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-export([purify_new_leaks/0, purify_format/2, purify_new_fds_inuse/0,
@@ -523,7 +524,7 @@ stick_all_sticky(Node,Sticky) ->
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-%% run_test_case_apply(Mod,Func,Args,Name,RunInit,TimetrapData) ->
+%% run_test_case_apply(Mod,Func,Args,Name,RunInit,TimetrapData,RejectIoReqs) ->
%% {Time,Value,Loc,Opts,Comment} | {died,Reason,unknown,Comment}
%%
%% Time = float() (seconds)
@@ -558,8 +559,12 @@ stick_all_sticky(Node,Sticky) ->
%% ScaleTimetrap indicates if test_server should attemp to automatically
%% compensate timetraps for runtime delays introduced by e.g. tools like
%% cover.
+%%
+%% RejectIoReqs (bool) is information about whether printouts to stdout
+%% should be visible in the minor log file or not.
-run_test_case_apply({CaseNum,Mod,Func,Args,Name,RunInit,TimetrapData}) ->
+run_test_case_apply({CaseNum,Mod,Func,Args,Name,
+ RunInit,TimetrapData,RejectIoReqs}) ->
purify_format("Test case #~w ~w:~w/1", [CaseNum, Mod, Func]),
case os:getenv("TS_RUN_VALGRIND") of
false ->
@@ -570,17 +575,19 @@ run_test_case_apply({CaseNum,Mod,Func,Args,Name,RunInit,TimetrapData}) ->
end,
test_server_h:testcase({Mod,Func,1}),
ProcBef = erlang:system_info(process_count),
- Result = run_test_case_apply(Mod, Func, Args, Name, RunInit, TimetrapData),
+ Result = run_test_case_apply(Mod, Func, Args, Name, RunInit,
+ TimetrapData, RejectIoReqs),
ProcAft = erlang:system_info(process_count),
purify_new_leaks(),
DetFail = get(test_server_detected_fail),
{Result,DetFail,ProcBef,ProcAft}.
-run_test_case_apply(Mod, Func, Args, Name, RunInit, TimetrapData) ->
+run_test_case_apply(Mod, Func, Args, Name, RunInit, TimetrapData, RejectIoReqs) ->
case get(test_server_job_dir) of
undefined ->
%% i'm a local target
- do_run_test_case_apply(Mod, Func, Args, Name, RunInit, TimetrapData);
+ do_run_test_case_apply(Mod, Func, Args, Name, RunInit,
+ TimetrapData, RejectIoReqs);
JobDir ->
%% i'm a remote target
case Args of
@@ -595,13 +602,14 @@ run_test_case_apply(Mod, Func, Args, Name, RunInit, TimetrapData) ->
Config2 = lists:keyreplace(priv_dir, 1, Config1,
{priv_dir,TargetPrivDir}),
do_run_test_case_apply(Mod, Func, [Config2], Name, RunInit,
- TimetrapData);
+ TimetrapData, RejectIoReqs);
_other ->
do_run_test_case_apply(Mod, Func, Args, Name, RunInit,
- TimetrapData)
+ TimetrapData, RejectIoReqs)
end
end.
-do_run_test_case_apply(Mod, Func, Args, Name, RunInit, TimetrapData) ->
+do_run_test_case_apply(Mod, Func, Args, Name, RunInit,
+ TimetrapData, RejectIoReqs) ->
{ok,Cwd} = file:get_cwd(),
Args2Print = case Args of
[Args1] when is_list(Args1) ->
@@ -628,7 +636,8 @@ do_run_test_case_apply(Mod, Func, Args, Name, RunInit, TimetrapData) ->
end),
group_leader(OldGLeader, self()),
put(test_server_detected_fail, []),
- run_test_case_msgloop(Ref, Pid, false, false, "", undefined, starting).
+ run_test_case_msgloop(Ref, Pid, false, RejectIoReqs, false, "",
+ undefined, starting).
%% Ugly bug (pre R5A):
%% If this process (group leader of the test case) terminates before
@@ -639,7 +648,7 @@ do_run_test_case_apply(Mod, Func, Args, Name, RunInit, TimetrapData) ->
%% A test case is known to have failed if it returns {'EXIT', _} tuple,
%% or sends a message {failed, File, Line} to it's group_leader
%%
-run_test_case_msgloop(Ref, Pid, CaptureStdout, Terminate,
+run_test_case_msgloop(Ref, Pid, CaptureStdout, RejectIoReqs, Terminate,
Comment, CurrConf, Status) ->
%% NOTE: Keep job_proxy_msgloop/0 up to date when changes
%% are made in this function!
@@ -655,7 +664,7 @@ run_test_case_msgloop(Ref, Pid, CaptureStdout, Terminate,
end,
receive
{test_case_initialized,Pid} ->
- run_test_case_msgloop(Ref,Pid,CaptureStdout,Terminate,
+ run_test_case_msgloop(Ref,Pid,CaptureStdout,RejectIoReqs,Terminate,
Comment,CurrConf,running);
Abort = {abort_current_testcase,_,_} when Status == starting ->
%% we're in init phase, must must postpone this operation
@@ -663,7 +672,7 @@ run_test_case_msgloop(Ref, Pid, CaptureStdout, Terminate,
%% gets killed)
self() ! Abort,
erlang:yield(),
- run_test_case_msgloop(Ref,Pid,CaptureStdout,Terminate,
+ run_test_case_msgloop(Ref,Pid,CaptureStdout,RejectIoReqs,Terminate,
Comment,CurrConf,Status);
{abort_current_testcase,Reason,From} ->
Line = case is_process_alive(Pid) of
@@ -694,82 +703,92 @@ run_test_case_msgloop(Ref, Pid, CaptureStdout, Terminate,
Error1
end
end,
- run_test_case_msgloop(Ref,Pid,CaptureStdout,Terminate,
+ run_test_case_msgloop(Ref,Pid,CaptureStdout,RejectIoReqs,Terminate,
NewComment,CurrConf,Status);
+ {permit_io,FromPid} ->
+ put({permit_io,FromPid},true),
+ run_test_case_msgloop(Ref,Pid,CaptureStdout,RejectIoReqs,Terminate,
+ Comment,CurrConf,Status);
{io_request,From,ReplyAs,{put_chars,io_lib,Func,[Format,Args]}}
when is_list(Format) ->
Msg = (catch io_lib:Func(Format,Args)),
- run_test_case_msgloop_io(ReplyAs,CaptureStdout,Msg,From,Func),
- run_test_case_msgloop(Ref,Pid,CaptureStdout,Terminate,
+ run_test_case_msgloop_io(From,ReplyAs,CaptureStdout,RejectIoReqs,
+ Msg,From,Func),
+ run_test_case_msgloop(Ref,Pid,CaptureStdout,RejectIoReqs,Terminate,
Comment,CurrConf,Status);
{io_request,From,ReplyAs,{put_chars,io_lib,Func,[Format,Args]}}
when is_atom(Format) ->
Msg = (catch io_lib:Func(Format,Args)),
- run_test_case_msgloop_io(ReplyAs,CaptureStdout,Msg,From,Func),
- run_test_case_msgloop(Ref,Pid,CaptureStdout,Terminate,
+ run_test_case_msgloop_io(From,ReplyAs,CaptureStdout,RejectIoReqs,
+ Msg,From,Func),
+ run_test_case_msgloop(Ref,Pid,CaptureStdout,RejectIoReqs,Terminate,
Comment,CurrConf,Status);
{io_request,From,ReplyAs,{put_chars,Bytes}} ->
- run_test_case_msgloop_io(
- ReplyAs,CaptureStdout,Bytes,From,put_chars),
- run_test_case_msgloop(Ref,Pid,CaptureStdout,Terminate,
+ run_test_case_msgloop_io(From,ReplyAs,CaptureStdout,RejectIoReqs,
+ Bytes,From,put_chars),
+ run_test_case_msgloop(Ref,Pid,CaptureStdout,RejectIoReqs,Terminate,
Comment,CurrConf,Status);
{io_request,From,ReplyAs,{put_chars,unicode,io_lib,Func,[Format,Args]}}
when is_list(Format) ->
Msg = unicode_to_latin1(catch io_lib:Func(Format,Args)),
- run_test_case_msgloop_io(ReplyAs,CaptureStdout,Msg,From,Func),
- run_test_case_msgloop(Ref,Pid,CaptureStdout,Terminate,
+ run_test_case_msgloop_io(From,ReplyAs,CaptureStdout,RejectIoReqs,
+ Msg,From,Func),
+ run_test_case_msgloop(Ref,Pid,CaptureStdout,RejectIoReqs,Terminate,
Comment,CurrConf,Status);
{io_request,From,ReplyAs,{put_chars,latin1,io_lib,Func,[Format,Args]}}
when is_list(Format) ->
Msg = (catch io_lib:Func(Format,Args)),
- run_test_case_msgloop_io(ReplyAs,CaptureStdout,Msg,From,Func),
- run_test_case_msgloop(Ref,Pid,CaptureStdout,Terminate,
+ run_test_case_msgloop_io(From,ReplyAs,CaptureStdout,RejectIoReqs,
+ Msg,From,Func),
+ run_test_case_msgloop(Ref,Pid,CaptureStdout,RejectIoReqs,Terminate,
Comment,CurrConf,Status);
{io_request,From,ReplyAs,{put_chars,unicode,io_lib,Func,[Format,Args]}}
when is_atom(Format) ->
Msg = unicode_to_latin1(catch io_lib:Func(Format,Args)),
- run_test_case_msgloop_io(ReplyAs,CaptureStdout,Msg,From,Func),
- run_test_case_msgloop(Ref,Pid,CaptureStdout,Terminate,
+ run_test_case_msgloop_io(From,ReplyAs,CaptureStdout,RejectIoReqs,
+ Msg,From,Func),
+ run_test_case_msgloop(Ref,Pid,CaptureStdout,RejectIoReqs,Terminate,
Comment,CurrConf,Status);
{io_request,From,ReplyAs,{put_chars,latin1,io_lib,Func,[Format,Args]}}
when is_atom(Format) ->
Msg = (catch io_lib:Func(Format,Args)),
- run_test_case_msgloop_io(ReplyAs,CaptureStdout,Msg,From,Func),
- run_test_case_msgloop(Ref,Pid,CaptureStdout,Terminate,
+ run_test_case_msgloop_io(From,ReplyAs,CaptureStdout,RejectIoReqs,
+ Msg,From,Func),
+ run_test_case_msgloop(Ref,Pid,CaptureStdout,RejectIoReqs,Terminate,
Comment,CurrConf,Status);
{io_request,From,ReplyAs,{put_chars,unicode,Bytes}} ->
- run_test_case_msgloop_io(
- ReplyAs,CaptureStdout,unicode_to_latin1(Bytes),From,put_chars),
- run_test_case_msgloop(Ref,Pid,CaptureStdout,Terminate,
+ run_test_case_msgloop_io(From,ReplyAs,CaptureStdout,RejectIoReqs,
+ unicode_to_latin1(Bytes),From,put_chars),
+ run_test_case_msgloop(Ref,Pid,CaptureStdout,RejectIoReqs,Terminate,
Comment,CurrConf,Status);
{io_request,From,ReplyAs,{put_chars,latin1,Bytes}} ->
- run_test_case_msgloop_io(
- ReplyAs,CaptureStdout,Bytes,From,put_chars),
- run_test_case_msgloop(Ref,Pid,CaptureStdout,Terminate,
+ run_test_case_msgloop_io(From,ReplyAs,CaptureStdout,RejectIoReqs,
+ Bytes,From,put_chars),
+ run_test_case_msgloop(Ref,Pid,CaptureStdout,RejectIoReqs,Terminate,
Comment,CurrConf,Status);
IoReq when element(1, IoReq) == io_request ->
%% something else, just pass it on
group_leader() ! IoReq,
- run_test_case_msgloop(Ref,Pid,CaptureStdout,Terminate,
+ run_test_case_msgloop(Ref,Pid,CaptureStdout,RejectIoReqs,Terminate,
Comment,CurrConf,Status);
{structured_io,ClientPid,Msg} ->
output(Msg, ClientPid),
- run_test_case_msgloop(Ref,Pid,CaptureStdout,Terminate,
+ run_test_case_msgloop(Ref,Pid,CaptureStdout,RejectIoReqs,Terminate,
Comment,CurrConf,Status);
{capture,NewCapture} ->
- run_test_case_msgloop(Ref,Pid,NewCapture,Terminate,
+ run_test_case_msgloop(Ref,Pid,NewCapture,RejectIoReqs,Terminate,
Comment,CurrConf,Status);
{sync_apply,From,MFA} ->
sync_local_or_remote_apply(false,From,MFA),
- run_test_case_msgloop(Ref,Pid,CaptureStdout,Terminate,
+ run_test_case_msgloop(Ref,Pid,CaptureStdout,RejectIoReqs,Terminate,
Comment,CurrConf,Status);
{sync_apply_proxy,Proxy,From,MFA} ->
sync_local_or_remote_apply(Proxy,From,MFA),
- run_test_case_msgloop(Ref,Pid,CaptureStdout,Terminate,
+ run_test_case_msgloop(Ref,Pid,CaptureStdout,RejectIoReqs,Terminate,
Comment,CurrConf,Status);
{printout,Detail,Format,Args} ->
print(Detail,Format,Args),
- run_test_case_msgloop(Ref,Pid,CaptureStdout,Terminate,
+ run_test_case_msgloop(Ref,Pid,CaptureStdout,RejectIoReqs,Terminate,
Comment,CurrConf,Status);
{comment,NewComment} ->
NewComment1 = test_server_ctrl:to_string(NewComment),
@@ -783,18 +802,20 @@ run_test_case_msgloop(Ref, Pid, CaptureStdout, Terminate,
Other ->
Other
end,
- run_test_case_msgloop(Ref,Pid,CaptureStdout,Terminate1,
+ run_test_case_msgloop(Ref,Pid,CaptureStdout,RejectIoReqs,Terminate1,
NewComment2,CurrConf,Status);
{read_comment,From} ->
From ! {self(),read_comment,Comment},
- run_test_case_msgloop(Ref,Pid,CaptureStdout,Terminate,
+ run_test_case_msgloop(Ref,Pid,CaptureStdout,RejectIoReqs,Terminate,
Comment,CurrConf,Status);
{set_curr_conf,From,NewCurrConf} ->
From ! {self(),set_curr_conf,ok},
- run_test_case_msgloop(Ref,Pid,CaptureStdout,Terminate,
+ run_test_case_msgloop(Ref,Pid,CaptureStdout,RejectIoReqs,Terminate,
Comment,NewCurrConf,Status);
{make_priv_dir,From} when CurrConf == undefined ->
- From ! {self(),make_priv_dir,{error,no_priv_dir_in_config}};
+ From ! {self(),make_priv_dir,{error,no_priv_dir_in_config}},
+ run_test_case_msgloop(Ref,Pid,CaptureStdout,RejectIoReqs,Terminate,
+ Comment,CurrConf,Status);
{make_priv_dir,From} ->
Result =
case proplists:get_value(priv_dir, element(2, CurrConf)) of
@@ -811,12 +832,12 @@ run_test_case_msgloop(Ref, Pid, CaptureStdout, Terminate,
end
end,
From ! {self(),make_priv_dir,Result},
- run_test_case_msgloop(Ref,Pid,CaptureStdout,Terminate,
+ run_test_case_msgloop(Ref,Pid,CaptureStdout,RejectIoReqs,Terminate,
Comment,CurrConf,Status);
{'EXIT',Pid,{Ref,Time,Value,Loc,Opts}} ->
RetVal = {Time/1000000,Value,mod_loc(Loc),Opts,Comment},
- run_test_case_msgloop(Ref,Pid,CaptureStdout,{true,RetVal},
- Comment,undefined,Status);
+ run_test_case_msgloop(Ref,Pid,CaptureStdout,RejectIoReqs,
+ {true,RetVal},Comment,undefined,Status);
{'EXIT',Pid,Reason} ->
case Reason of
{timetrap_timeout,TVal,Loc} ->
@@ -827,7 +848,8 @@ run_test_case_msgloop(Ref, Pid, CaptureStdout, Terminate,
spawn_fw_call(FwMod,FwFunc,CurrConf,Pid,
{framework_error,{timetrap,TVal}},
unknown,self()),
- run_test_case_msgloop(Ref,Pid,CaptureStdout,
+ run_test_case_msgloop(Ref,Pid,
+ CaptureStdout,RejectIoReqs,
Terminate,Comment,
undefined,Status);
Loc1 ->
@@ -860,7 +882,8 @@ run_test_case_msgloop(Ref, Pid, CaptureStdout, Terminate,
Loc1,self()),
undefined
end,
- run_test_case_msgloop(Ref,Pid,CaptureStdout,
+ run_test_case_msgloop(Ref,Pid,
+ CaptureStdout,RejectIoReqs,
Terminate,Comment,
NewCurrConf,Status)
end;
@@ -877,15 +900,16 @@ run_test_case_msgloop(Ref, Pid, CaptureStdout, Terminate,
{timetrap_timeout,TVal},
Loc1,self())
end,
- run_test_case_msgloop(Ref,Pid,CaptureStdout,Terminate,
- Comment,CurrConf,Status);
+ run_test_case_msgloop(Ref,Pid,CaptureStdout,RejectIoReqs,
+ Terminate,Comment,CurrConf,Status);
{testcase_aborted,ErrorMsg={user_timetrap_error,_},AbortLoc} ->
%% user timetrap function caused exit
%% during start of test case
{Mod,Func} = get_mf(mod_loc(AbortLoc)),
spawn_fw_call(Mod,Func,CurrConf,Pid,
ErrorMsg,unknown,self()),
- run_test_case_msgloop(Ref,Pid,CaptureStdout,
+ run_test_case_msgloop(Ref,Pid,
+ CaptureStdout,RejectIoReqs,
Terminate,Comment,
undefined,Status);
{testcase_aborted,AbortReason,AbortLoc} ->
@@ -896,7 +920,8 @@ run_test_case_msgloop(Ref, Pid, CaptureStdout, Terminate,
spawn_fw_call(FwMod,FwFunc,CurrConf,Pid,
{framework_error,ErrorMsg},
unknown,self()),
- run_test_case_msgloop(Ref,Pid,CaptureStdout,
+ run_test_case_msgloop(Ref,Pid,
+ CaptureStdout,RejectIoReqs,
Terminate,Comment,
undefined,Status);
Loc1 ->
@@ -928,7 +953,8 @@ run_test_case_msgloop(Ref, Pid, CaptureStdout, Terminate,
ErrorMsg,Loc1,self()),
undefined
end,
- run_test_case_msgloop(Ref,Pid,CaptureStdout,
+ run_test_case_msgloop(Ref,Pid,
+ CaptureStdout,RejectIoReqs,
Terminate,Comment,
NewCurrConf,Status)
end;
@@ -943,14 +969,14 @@ run_test_case_msgloop(Ref, Pid, CaptureStdout, Terminate,
spawn_fw_call(Mod,Func,CurrConf,Pid,
testcase_aborted_or_killed,
unknown,self()),
- run_test_case_msgloop(Ref,Pid,CaptureStdout,Terminate,
- Comment,CurrConf,Status);
+ run_test_case_msgloop(Ref,Pid,CaptureStdout,RejectIoReqs,
+ Terminate,Comment,CurrConf,Status);
{fw_error,{FwMod,FwFunc,FwError}} ->
spawn_fw_call(FwMod,FwFunc,CurrConf,Pid,
{framework_error,FwError},
unknown,self()),
- run_test_case_msgloop(Ref,Pid,CaptureStdout,Terminate,
- Comment,CurrConf,Status);
+ run_test_case_msgloop(Ref,Pid,CaptureStdout,RejectIoReqs,
+ Terminate,Comment,CurrConf,Status);
_Other ->
%% the testcase has terminated because of Reason (e.g. an exit
%% because a linked process failed)
@@ -960,8 +986,8 @@ run_test_case_msgloop(Ref, Pid, CaptureStdout, Terminate,
end,
spawn_fw_call(Mod,Func,CurrConf,Pid,
Reason,unknown,self()),
- run_test_case_msgloop(Ref,Pid,CaptureStdout,Terminate,
- Comment,CurrConf,Status)
+ run_test_case_msgloop(Ref,Pid,CaptureStdout,RejectIoReqs,
+ Terminate,Comment,CurrConf,Status)
end;
{EndConfPid,{call_end_conf,Data,_Result}} ->
case CurrConf of
@@ -969,11 +995,11 @@ run_test_case_msgloop(Ref, Pid, CaptureStdout, Terminate,
{_Mod,_Func,TCPid,TCExitReason,Loc} = Data,
spawn_fw_call(Mod,Func,CurrConf,TCPid,
TCExitReason,Loc,self()),
- run_test_case_msgloop(Ref,Pid,CaptureStdout,Terminate,
- Comment,undefined,Status);
+ run_test_case_msgloop(Ref,Pid,CaptureStdout,RejectIoReqs,
+ Terminate,Comment,undefined,Status);
_ ->
- run_test_case_msgloop(Ref,Pid,CaptureStdout,Terminate,
- Comment,CurrConf,Status)
+ run_test_case_msgloop(Ref,Pid,CaptureStdout,RejectIoReqs,
+ Terminate,Comment,CurrConf,Status)
end;
{_FwCallPid,fw_notify_done,{T,Value,Loc,Opts,AddToComment}} ->
%% the framework has been notified, we're finished
@@ -993,8 +1019,8 @@ run_test_case_msgloop(Ref, Pid, CaptureStdout, Terminate,
end,
{T,Value,Loc,Opts,Comment1}
end,
- run_test_case_msgloop(Ref,Pid,CaptureStdout,{true,RetVal},
- Comment,undefined,Status);
+ run_test_case_msgloop(Ref,Pid,CaptureStdout,RejectIoReqs,
+ {true,RetVal},Comment,undefined,Status);
{'EXIT',_FwCallPid,{fw_notify_done,Func,Error}} ->
%% a framework function failed
CB = os:getenv("TEST_SERVER_FRAMEWORK"),
@@ -1005,13 +1031,13 @@ run_test_case_msgloop(Ref, Pid, CaptureStdout, Terminate,
{list_to_atom(CB),Func}
end,
RetVal = {died,{framework_error,Loc,Error},Loc,"Framework error"},
- run_test_case_msgloop(Ref,Pid,CaptureStdout,{true,RetVal},
- Comment,undefined,Status);
+ run_test_case_msgloop(Ref,Pid,CaptureStdout,RejectIoReqs,
+ {true,RetVal},Comment,undefined,Status);
{failed,File,Line} ->
put(test_server_detected_fail,
[{File, Line}| get(test_server_detected_fail)]),
- run_test_case_msgloop(Ref,Pid,CaptureStdout,Terminate,
- Comment,CurrConf,Status);
+ run_test_case_msgloop(Ref,Pid,CaptureStdout,RejectIoReqs,
+ Terminate,Comment,CurrConf,Status);
{user_timetrap,Pid,_TrapTime,StartTime,E={user_timetrap_error,_},_} ->
case update_user_timetraps(Pid, StartTime) of
@@ -1020,8 +1046,8 @@ run_test_case_msgloop(Ref, Pid, CaptureStdout, Terminate,
ignore ->
ok
end,
- run_test_case_msgloop(Ref,Pid,CaptureStdout,Terminate,
- Comment,CurrConf,Status);
+ run_test_case_msgloop(Ref,Pid,CaptureStdout,RejectIoReqs,
+ Terminate,Comment,CurrConf,Status);
{user_timetrap,Pid,TrapTime,StartTime,ElapsedTime,Scale} ->
%% a user timetrap is triggered, ignore it if new
%% timetrap has been started since
@@ -1036,49 +1062,57 @@ run_test_case_msgloop(Ref, Pid, CaptureStdout, Terminate,
ignore ->
ok
end,
- run_test_case_msgloop(Ref,Pid,CaptureStdout,Terminate,
- Comment,CurrConf,Status);
+ run_test_case_msgloop(Ref,Pid,CaptureStdout,RejectIoReqs,
+ Terminate,Comment,CurrConf,Status);
{timetrap_cancel_one,Handle,_From} ->
timetrap_cancel_one(Handle, false),
- run_test_case_msgloop(Ref,Pid,CaptureStdout,Terminate,
- Comment,CurrConf,Status);
+ run_test_case_msgloop(Ref,Pid,CaptureStdout,RejectIoReqs,
+ Terminate,Comment,CurrConf,Status);
{timetrap_cancel_all,TCPid,_From} ->
timetrap_cancel_all(TCPid, false),
- run_test_case_msgloop(Ref,Pid,CaptureStdout,Terminate,
- Comment,CurrConf,Status);
+ run_test_case_msgloop(Ref,Pid,CaptureStdout,RejectIoReqs,
+ Terminate,Comment,CurrConf,Status);
{get_timetrap_info,TCPid,From} ->
Info = get_timetrap_info(TCPid, false),
From ! {self(),get_timetrap_info,Info},
- run_test_case_msgloop(Ref,Pid,CaptureStdout,Terminate,
- Comment,CurrConf,Status);
+ run_test_case_msgloop(Ref,Pid,CaptureStdout,RejectIoReqs,
+ Terminate,Comment,CurrConf,Status);
_Other when not is_tuple(_Other) ->
%% ignore anything not generated by test server
- run_test_case_msgloop(Ref,Pid,CaptureStdout,Terminate,
- Comment,CurrConf,Status);
+ run_test_case_msgloop(Ref,Pid,CaptureStdout,RejectIoReqs,
+ Terminate,Comment,CurrConf,Status);
_Other when element(1, _Other) /= 'EXIT',
element(1, _Other) /= started,
element(1, _Other) /= finished,
element(1, _Other) /= print ->
%% ignore anything not generated by test server
- run_test_case_msgloop(Ref,Pid,CaptureStdout,Terminate,
- Comment,CurrConf,Status)
+ run_test_case_msgloop(Ref,Pid,CaptureStdout,RejectIoReqs,
+ Terminate,Comment,CurrConf,Status)
after Timeout ->
ReturnValue
end.
-run_test_case_msgloop_io(ReplyAs,CaptureStdout,Msg,From,Func) ->
+run_test_case_msgloop_io(From,ReplyAs,CaptureStdout,RejectIoReqs,
+ Msg,From,Func) ->
case Msg of
{'EXIT',_} ->
From ! {io_reply,ReplyAs,{error,Func}};
_ ->
From ! {io_reply,ReplyAs,ok}
end,
- if CaptureStdout /= false ->
- CaptureStdout ! {captured,Msg};
- true ->
+ Proceed = if RejectIoReqs -> get({permit_io,From});
+ true -> true
+ end,
+ if Proceed ->
+ if CaptureStdout /= false ->
+ CaptureStdout ! {captured,Msg};
+ true ->
+ ok
+ end,
+ output({minor,Msg},From);
+ true ->
ok
- end,
- output({minor,Msg},From).
+ end.
output(Msg,Sender) ->
local_or_remote_apply({test_server_ctrl,output,[Msg,Sender]}).
@@ -1097,7 +1131,8 @@ call_end_conf(Mod,Func,TCPid,TCExitReason,Loc,Conf,TVal) ->
{'EXIT',Why} ->
timer:sleep(1),
group_leader() ! {printout,12,
- "WARNING! ~p:end_per_testcase(~p, ~p)"
+ "WARNING! "
+ "~p:end_per_testcase(~p, ~p)"
" crashed!\n\tReason: ~p\n",
[Mod,Func,Conf,Why]};
_ ->
@@ -1213,13 +1248,18 @@ spawn_fw_call(FwMod,FwFunc,_,_Pid,{framework_error,FwError},_,SendTo) ->
end,
spawn_link(FwCall);
-spawn_fw_call(Mod,Func,_,Pid,Error,Loc,SendTo) ->
+spawn_fw_call(Mod,Func,CurrConf,Pid,Error,Loc,SendTo) ->
+ {Mod1,Func1} =
+ case {Mod,Func,CurrConf} of
+ {undefined,undefined,{{M,F},_}} -> {M,F};
+ _ -> {Mod,Func}
+ end,
FwCall =
fun() ->
%% set group leader so that printouts/comments
%% from the framework get printed in the logs
group_leader(SendTo, self()),
- case catch fw_error_notify(Mod,Func,[],
+ case catch fw_error_notify(Mod1,Func1,[],
Error,Loc) of
{'EXIT',FwErrorNotifyErr} ->
exit({fw_notify_done,error_notification,
@@ -1228,7 +1268,7 @@ spawn_fw_call(Mod,Func,_,Pid,Error,Loc,SendTo) ->
ok
end,
Conf = [{tc_status,{failed,timetrap_timeout}}],
- case catch do_end_tc_call(Mod,Func, Loc,
+ case catch do_end_tc_call(Mod1,Func1, Loc,
{Pid,Error,[Conf]},Error) of
{'EXIT',FwEndTCErr} ->
exit({fw_notify_done,end_tc,FwEndTCErr});
@@ -1959,6 +1999,13 @@ messages_get() ->
test_server_sup:messages_get([]).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% permit_io(GroupLeader, FromPid) -> ok
+%%
+%% Make sure proceeding IO from FromPid won't get rejected
+permit_io(GroupLeader, FromPid) ->
+ GroupLeader ! {permit_io,FromPid}.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% sleep(Time) -> ok
%% Time = integer() | float() | infinity
%%
@@ -2061,31 +2108,40 @@ fail() ->
%% Break a test case so part of the test can be done manually.
%% Use continue/0 to continue.
break(Comment) ->
- case erase(test_server_timetraps) of
- undefined -> ok;
- List -> lists:foreach(fun({Ref,_,_}) ->
- timetrap_cancel(Ref)
- end, List)
- end,
+ break(?MODULE, Comment).
+
+break(CBM, Comment) ->
+ break(CBM, '', Comment).
+
+break(CBM, TestCase, Comment) ->
+ timetrap_cancel(),
+ {TCName,CntArg,PName} =
+ if TestCase == '' ->
+ {"", "", test_server_break_process};
+ true ->
+ Str = atom_to_list(TestCase),
+ {[32 | Str], Str,
+ list_to_atom("test_server_break_process_" ++ Str)}
+ end,
io:format(user,
"\n\n\n--- SEMIAUTOMATIC TESTING ---"
- "\nThe test case executes on process ~w"
+ "\nThe test case~s executes on process ~w"
"\n\n\n~s"
"\n\n\n-----------------------------\n\n"
- "Continue with --> test_server:continue().\n",
- [self(),Comment]),
- case whereis(test_server_break_process) of
+ "Continue with --> ~w:continue(~s).\n",
+ [TCName,self(),Comment,CBM,CntArg]),
+ case whereis(PName) of
undefined ->
- spawn_break_process(self());
+ spawn_break_process(self(), PName);
OldBreakProcess ->
OldBreakProcess ! cancel,
- spawn_break_process(self())
+ spawn_break_process(self(), PName)
end,
receive continue -> ok end.
-spawn_break_process(Pid) ->
+spawn_break_process(Pid, PName) ->
spawn(fun() ->
- register(test_server_break_process,self()),
+ register(PName, self()),
receive
continue -> continue(Pid);
cancel -> ok
@@ -2094,13 +2150,19 @@ spawn_break_process(Pid) ->
continue() ->
case whereis(test_server_break_process) of
- undefined ->
- ok;
- BreakProcess ->
- BreakProcess ! continue
+ undefined -> ok;
+ BreakProcess -> BreakProcess ! continue
end.
-continue(Pid) ->
+continue(TestCase) when is_atom(TestCase) ->
+ PName = list_to_atom("test_server_break_process_" ++
+ atom_to_list(TestCase)),
+ case whereis(PName) of
+ undefined -> ok;
+ BreakProcess -> BreakProcess ! continue
+ end;
+
+continue(Pid) when is_pid(Pid) ->
Pid ! continue.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
diff --git a/lib/test_server/src/test_server_ctrl.erl b/lib/test_server/src/test_server_ctrl.erl
index 5ed296d215..df2187bc04 100644
--- a/lib/test_server/src/test_server_ctrl.erl
+++ b/lib/test_server/src/test_server_ctrl.erl
@@ -162,7 +162,7 @@
-export([jobs/0, run_test/1, wait_finish/0, idle_notify/1,
abort_current_testcase/1, abort/0]).
-export([start_get_totals/1, stop_get_totals/0]).
--export([get_levels/0, set_levels/3]).
+-export([reject_io_reqs/1, get_levels/0, set_levels/3]).
-export([multiply_timetraps/1, scale_timetraps/1, get_timetrap_parameters/0]).
-export([create_priv_dir/1]).
-export([cover/2, cover/3, cover/7,
@@ -218,8 +218,9 @@
-define(auto_skip_color, "#FFA64D").
-define(user_skip_color, "#FF8000").
+-define(sortable_table_name, "SortableTable").
--record(state,{jobs=[],levels={1,19,10},
+-record(state,{jobs=[], levels={1,19,10}, reject_io_reqs=false,
multiply_timetraps=1, scale_timetraps=true,
create_priv_dir=auto_per_run, finish=false,
target_info, trc=false, cover=false, wait_for_node=[],
@@ -498,6 +499,9 @@ get_levels() ->
set_levels(Show, Major, Minor) ->
controller_call({set_levels,Show,Major,Minor}).
+reject_io_reqs(Bool) ->
+ controller_call({reject_io_reqs,Bool}).
+
multiply_timetraps(N) ->
controller_call({multiply_timetraps,N}).
@@ -815,6 +819,7 @@ handle_call({add_job,Dir,Name,TopCase,Skip}, _From, State) ->
[SpecName,{State#state.multiply_timetraps,
State#state.scale_timetraps}],
LogDir, Name, State#state.levels,
+ State#state.reject_io_reqs,
State#state.create_priv_dir,
State#state.testcase_callback, ExtraTools1),
NewJobs = [{Name,Pid}|State#state.jobs],
@@ -825,6 +830,7 @@ handle_call({add_job,Dir,Name,TopCase,Skip}, _From, State) ->
[SpecList,{State#state.multiply_timetraps,
State#state.scale_timetraps}],
LogDir, Name, State#state.levels,
+ State#state.reject_io_reqs,
State#state.create_priv_dir,
State#state.testcase_callback, ExtraTools1),
NewJobs = [{Name,Pid}|State#state.jobs],
@@ -843,6 +849,7 @@ handle_call({add_job,Dir,Name,TopCase,Skip}, _From, State) ->
{State#state.multiply_timetraps,
State#state.scale_timetraps}],
LogDir, Name, State#state.levels,
+ State#state.reject_io_reqs,
State#state.create_priv_dir,
State#state.testcase_callback, ExtraTools1),
NewJobs = [{Name,Pid}|State#state.jobs],
@@ -975,6 +982,15 @@ handle_call({set_levels,Show,Major,Minor}, _From, State) ->
{reply,ok,State#state{levels={Show,Major,Minor}}};
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% handle_call({reject_io_reqs,Bool}, _, State) -> ok
+%% Bool = bool()
+%%
+%% May be used to switch off stdout printouts to the minor log file
+
+handle_call({reject_io_reqs,Bool}, _From, State) ->
+ {reply,ok,State#state{reject_io_reqs=Bool}};
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% handle_call({multiply_timetraps,N}, _, State) -> ok
%% N = integer() | infinity
%%
@@ -1340,14 +1356,15 @@ kill_all_jobs([]) ->
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-%% spawn_tester(Mod, Func, Args, Dir, Name, Levels, CreatePrivDir,
-%% TestCaseCallback, ExtraTools) -> Pid
+%% spawn_tester(Mod, Func, Args, Dir, Name, Levels, RejectIoReqs,
+%% CreatePrivDir, TestCaseCallback, ExtraTools) -> Pid
%% Mod = atom()
%% Func = atom()
%% Args = [term(),...]
%% Dir = string()
%% Name = string()
%% Levels = {integer(),integer(),integer()}
+%% RejectIoReqs = bool()
%% CreatePrivDir = auto_per_run | manual_per_tc | auto_per_tc
%% TestCaseCallback = {CBMod,CBFunc} | undefined
%% ExtraTools = [ExtraTool,...]
@@ -1359,14 +1376,14 @@ kill_all_jobs([]) ->
%% When the named function is done executing, a summary of the results
%% is printed to the log files.
-spawn_tester(Mod, Func, Args, Dir, Name, Levels,
+spawn_tester(Mod, Func, Args, Dir, Name, Levels, RejectIoReqs,
CreatePrivDir, TCCallback, ExtraTools) ->
spawn_link(
- fun() -> init_tester(Mod, Func, Args, Dir, Name, Levels,
+ fun() -> init_tester(Mod, Func, Args, Dir, Name, Levels, RejectIoReqs,
CreatePrivDir, TCCallback, ExtraTools)
end).
-init_tester(Mod, Func, Args, Dir, Name, {SumLev,MajLev,MinLev},
+init_tester(Mod, Func, Args, Dir, Name, {SumLev,MajLev,MinLev}, RejectIoReqs,
CreatePrivDir, TCCallback, ExtraTools) ->
process_flag(trap_exit, true),
put(test_server_name, Name),
@@ -1378,6 +1395,7 @@ init_tester(Mod, Func, Args, Dir, Name, {SumLev,MajLev,MinLev},
put(test_server_summary_level, SumLev),
put(test_server_major_level, MajLev),
put(test_server_minor_level, MinLev),
+ put(test_server_reject_io_reqs, RejectIoReqs),
put(test_server_create_priv_dir, CreatePrivDir),
put(test_server_random_seed, proplists:get_value(random_seed, ExtraTools)),
put(test_server_testcase_callback, TCCallback),
@@ -1424,8 +1442,10 @@ init_tester(Mod, Func, Args, Dir, Name, {SumLev,MajLev,MinLev},
end,
OkN = get(test_server_ok),
FailedN = get(test_server_failed),
- print(html,"<tr><td></td><td><b>TOTAL</b></td><td></td><td></td><td></td>"
- "<td>~.3fs</td><td><b>~s</b></td><td>~p Ok, ~p Failed~s of ~p</td></tr>\n",
+ print(html,"\n</tbody>\n<tfoot>\n"
+ "<tr><td></td><td><b>TOTAL</b></td><td></td><td></td><td></td>"
+ "<td>~.3fs</td><td><b>~s</b></td><td>~p Ok, ~p Failed~s of ~p</td></tr>\n"
+ "</tfoot>\n",
[Time,SuccessStr,OkN,FailedN,SkipStr,OkN+FailedN+SkippedN]).
%% timer:tc/3
@@ -1486,7 +1506,7 @@ stop_extra_tools([], _) ->
%% Reads the named test suite specification file, and executes it.
%%
%% This function is meant to be called by a process created by
-%% spawn_tester/7, which sets up some necessary dictionary values.
+%% spawn_tester/10, which sets up some necessary dictionary values.
do_spec(SpecName, TimetrapSpec) when is_list(SpecName) ->
case file:consult(SpecName) of
@@ -1535,7 +1555,7 @@ do_spec(SpecName, TimetrapSpec) when is_list(SpecName) ->
%% should not be used. Use a configuration test case instead.
%%
%% This function is meant to be called by a process created by
-%% spawn_tester/7, which sets up some necessary dictionary values.
+%% spawn_tester/10, which sets up some necessary dictionary values.
do_spec_list(TermList0, TimetrapSpec) ->
Nodes = [],
@@ -1702,7 +1722,7 @@ add_mod(Mod, Mods) ->
%% configuration information into the log files.
%%
%% This function is meant to be called by a process created by
-%% spawn_tester/7, which sets up some necessary dictionary values.
+%% spawn_tester/10, which sets up some necessary dictionary values.
do_test_cases(TopCases, SkipCases,
Config, MultiplyTimetrap) when is_integer(MultiplyTimetrap);
MultiplyTimetrap == infinity ->
@@ -1741,7 +1761,8 @@ do_test_cases(TopCases, SkipCases,
test_server_sup:framework_call(report, [tests_start,{Test,N}]),
{Header,Footer} =
case test_server_sup:framework_call(get_html_wrapper,
- [TestDescr,true,TestDir], "") of
+ [TestDescr,true,TestDir,
+ {[],[2,3,4,7,8],[1,6]}], "") of
Empty when (Empty == "") ; (element(2,Empty) == "") ->
put(basic_html, true),
{["<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 3.2 Final//EN\">\n",
@@ -1803,14 +1824,15 @@ do_test_cases(TopCases, SkipCases,
[?suitelog_name,?coverlog_name]),
print(html,
"<p>~s</p>\n" ++
- xhtml("<table bgcolor=\"white\" border=\"3\" cellpadding=\"5\">",
- "<table>") ++
- "<tr><th>Num</th><th>Module</th><th>Group</th>" ++
- "<th>Case</th><th>Log</th><th>Time</th><th>Result</th>" ++
- "<th>Comment</th></tr>\n",
- [print_if_known(N, {"<i>Executing <b>~p</b> test cases...</i>\n",[N]},
+ xhtml("<table bgcolor=\"white\" border=\"3\" cellpadding=\"5\">",
+ ["<table id=\"",?sortable_table_name,"\">\n",
+ "<thead>\n"]) ++
+ "<tr><th>Num</th><th>Module</th><th>Group</th>" ++
+ "<th>Case</th><th>Log</th><th>Time</th><th>Result</th>" ++
+ "<th>Comment</th></tr>\n</thead>\n<tbody>\n",
+ [print_if_known(N, {"<i>Executing <b>~p</b> test cases...</i>" ++
+ xhtml("\n<br>\n", "\n<br />\n"),[N]},
{"",[]})]),
- print(html, xhtml("<br>", "<br />")),
print(major, "=cases ~p", [get(test_server_cases)]),
print(major, "=user ~s", [TI#target_info.username]),
@@ -1964,7 +1986,8 @@ start_minor_log_file1(Mod, Func, LogDir, AbsName) ->
{Header,Footer} =
case test_server_sup:framework_call(get_html_wrapper,
[TestDescr,false,
- filename:dirname(AbsName)], "") of
+ filename:dirname(AbsName),
+ undefined], "") of
Empty when (Empty == "") ; (element(2,Empty) == "") ->
put(basic_html, true),
{["<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 3.2 Final//EN\">\n",
@@ -2094,7 +2117,7 @@ html_possibly_convert(Src, SrcInfo, Dest) ->
Header =
case test_server_sup:framework_call(get_html_wrapper,
["Module "++Src,false,
- OutDir], "") of
+ OutDir,undefined], "") of
Empty when (Empty == "") ; (element(2,Empty) == "") ->
["<!DOCTYPE HTML PUBLIC",
"\"-//W3C//DTD HTML 3.2 Final//EN\">\n",
@@ -3809,10 +3832,12 @@ run_test_case1(Ref, Num, Mod, Func, Args, RunInit, Where,
MinorBase,MinorBase]),
do_if_parallel(Main, ok, fun erlang:yield/0),
+
+ RejectIoReqs = get(test_server_reject_io_reqs),
%% run the test case
{Result,DetectedFail,ProcsBefore,ProcsAfter} =
run_test_case_apply(Num, Mod, Func, [UpdatedArgs], get_name(Mode),
- RunInit, Where, TimetrapData),
+ RunInit, Where, TimetrapData, RejectIoReqs),
{Time,RetVal,Loc,Opts,Comment} =
case Result of
Normal={_Time,_RetVal,_Loc,_Opts,_Comment} -> Normal;
@@ -4431,7 +4456,7 @@ do_format_exception(Reason={Error,Stack}) ->
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% run_test_case_apply(CaseNum, Mod, Func, Args, Name, RunInit,
-%% Where, TimetrapData) ->
+%% Where, TimetrapData, RejectIoReqs) ->
%% {{Time,RetVal,Loc,Opts,Comment},DetectedFail,ProcessesBefore,ProcessesAfter} |
%% {{died,Reason,unknown,Comment},DetectedFail,ProcessesBefore,ProcessesAfter}
%% Name = atom()
@@ -4450,19 +4475,21 @@ do_format_exception(Reason={Error,Stack}) ->
%% sent over socket to target, and test_server runs the case and sends the
%% result back over the socket. Else test_server runs the case directly on host.
-run_test_case_apply(CaseNum, Mod, Func, Args, Name, RunInit, host, TimetrapData) ->
+run_test_case_apply(CaseNum, Mod, Func, Args, Name, RunInit, host,
+ TimetrapData, RejectIoReqs) ->
test_server:run_test_case_apply({CaseNum,Mod,Func,Args,Name,RunInit,
- TimetrapData});
-run_test_case_apply(CaseNum, Mod, Func, Args, Name, RunInit, target, TimetrapData) ->
+ TimetrapData,RejectIoReqs});
+run_test_case_apply(CaseNum, Mod, Func, Args, Name, RunInit, target,
+ TimetrapData, RejectIoReqs) ->
case get(test_server_ctrl_job_sock) of
undefined ->
%% local target
test_server:run_test_case_apply({CaseNum,Mod,Func,Args,Name,RunInit,
- TimetrapData});
+ TimetrapData,RejectIoReqs});
JobSock ->
%% remote target
request(JobSock, {test_case,{CaseNum,Mod,Func,Args,Name,RunInit,
- TimetrapData}}),
+ TimetrapData,RejectIoReqs}}),
read_job_sock_loop(JobSock)
end.
diff --git a/lib/test_server/vsn.mk b/lib/test_server/vsn.mk
index a1f4559083..aecf595f3f 100644
--- a/lib/test_server/vsn.mk
+++ b/lib/test_server/vsn.mk
@@ -1 +1 @@
-TEST_SERVER_VSN = 3.5.1
+TEST_SERVER_VSN = 3.5.2
diff --git a/lib/wx/aclocal.m4 b/lib/wx/aclocal.m4
index 339a15a2bb..a76594d86f 100644
--- a/lib/wx/aclocal.m4
+++ b/lib/wx/aclocal.m4
@@ -59,6 +59,7 @@ AC_ARG_VAR(erl_xcomp_isysroot, [Absolute cross system root include path (only us
dnl Cross compilation variables
AC_ARG_VAR(erl_xcomp_bigendian, [big endian system: yes|no (only used when cross compiling)])
+AC_ARG_VAR(erl_xcomp_double_middle_endian, [double-middle-endian system: yes|no (only used when cross compiling)])
AC_ARG_VAR(erl_xcomp_linux_clock_gettime_correction, [clock_gettime() can be used for time correction: yes|no (only used when cross compiling)])
AC_ARG_VAR(erl_xcomp_linux_nptl, [have Native POSIX Thread Library: yes|no (only used when cross compiling)])
AC_ARG_VAR(erl_xcomp_linux_usable_sigusrx, [SIGUSR1 and SIGUSR2 can be used: yes|no (only used when cross compiling)])
@@ -606,6 +607,103 @@ ifelse([$5], , , [$5
fi
])
+dnl ----------------------------------------------------------------------
+dnl
+dnl AC_DOUBLE_MIDDLE_ENDIAN
+dnl
+dnl Checks whether doubles are represented in "middle-endian" format.
+dnl Sets ac_cv_double_middle_endian={no,yes,unknown} accordingly,
+dnl as well as DOUBLE_MIDDLE_ENDIAN.
+dnl
+dnl
+
+AC_DEFUN([AC_C_DOUBLE_MIDDLE_ENDIAN],
+[AC_CACHE_CHECK(whether double word ordering is middle-endian, ac_cv_c_double_middle_endian,
+[# It does not; compile a test program.
+AC_RUN_IFELSE(
+[AC_LANG_SOURCE([[#include <stdlib.h>
+
+int
+main(void)
+{
+ int i = 0;
+ int zero = 0;
+ int bigendian;
+ int zero_index = 0;
+
+ union
+ {
+ long int l;
+ char c[sizeof (long int)];
+ } u;
+
+ /* we'll use the one with 32-bit words */
+ union
+ {
+ double d;
+ unsigned int c[2];
+ } vint;
+
+ union
+ {
+ double d;
+ unsigned long c[2];
+ } vlong;
+
+ union
+ {
+ double d;
+ unsigned short c[2];
+ } vshort;
+
+
+ /* Are we little or big endian? From Harbison&Steele. */
+ u.l = 1;
+ bigendian = (u.c[sizeof (long int) - 1] == 1);
+
+ zero_index = bigendian ? 1 : 0;
+
+ vint.d = 1.0;
+ vlong.d = 1.0;
+ vshort.d = 1.0;
+
+ if (sizeof(unsigned int) == 4)
+ {
+ if (vint.c[zero_index] != 0)
+ zero = 1;
+ }
+ else if (sizeof(unsigned long) == 4)
+ {
+ if (vlong.c[zero_index] != 0)
+ zero = 1;
+ }
+ else if (sizeof(unsigned short) == 4)
+ {
+ if (vshort.c[zero_index] != 0)
+ zero = 1;
+ }
+
+ exit (zero);
+}
+]])],
+ [ac_cv_c_double_middle_endian=no],
+ [ac_cv_c_double_middle_endian=yes],
+ [ac_cv_c_double_middle=unknown])])
+case $ac_cv_c_double_middle_endian in
+ yes)
+ m4_default([$1],
+ [AC_DEFINE([DOUBLE_MIDDLE_ENDIAN], 1,
+ [Define to 1 if your processor stores the words in a double in
+ middle-endian format (like some ARMs).])]) ;;
+ no)
+ $2 ;;
+ *)
+ m4_default([$3],
+ [AC_MSG_WARN([unknown double endianness
+presetting ac_cv_c_double_middle_endian=no (or yes) will help])]) ;;
+esac
+])# AC_C_DOUBLE_MIDDLE_ENDIAN
+
dnl ----------------------------------------------------------------------
dnl
@@ -1337,6 +1435,14 @@ if test "$ac_cv_c_bigendian" = "yes"; then
AC_DEFINE(ETHR_BIGENDIAN, 1, [Define if bigendian])
fi
+case X$erl_xcomp_double_middle_endian in
+ X) ;;
+ Xyes|Xno|Xunknown) ac_cv_c_double_middle_endian=$erl_xcomp_double_middle_endian;;
+ *) AC_MSG_ERROR([Bad erl_xcomp_double_middle_endian value: $erl_xcomp_double_middle_endian]);;
+esac
+
+AC_C_DOUBLE_MIDDLE_ENDIAN
+
AC_ARG_ENABLE(native-ethr-impls,
AS_HELP_STRING([--disable-native-ethr-impls],
[disable native ethread implementations]),
diff --git a/xcomp/README.md b/xcomp/README.md
index 5f4b36bdca..7217fea5fb 100644
--- a/xcomp/README.md
+++ b/xcomp/README.md
@@ -462,6 +462,10 @@ When a variable has been set, no warning will be issued.
`configure` will fail unless this variable is set. Since no default
value is used, `configure` will try to figure this out automatically.
+* `erl_xcomp_double_middle` - `yes|no`. Defaults to `no`.
+ If `yes`, the target system must have doubles in "middle-endian" format. If
+ `no`, it has "regular" endianness.
+
* `erl_xcomp_clock_gettime_cpu_time` - `yes|no`. Defaults to `no`. If `yes`,
the target system must have a working `clock_gettime()` implementation
that can be used for retrieving process CPU time.
diff --git a/xcomp/erl-xcomp-TileraMDE2.0-tilepro.conf b/xcomp/erl-xcomp-TileraMDE2.0-tilepro.conf
index f9fff0fa8e..287cb787d9 100644
--- a/xcomp/erl-xcomp-TileraMDE2.0-tilepro.conf
+++ b/xcomp/erl-xcomp-TileraMDE2.0-tilepro.conf
@@ -187,6 +187,15 @@ erl_xcomp_sysroot="$TILERA_ROOT/tile"
# value is used, `configure' will try to figure this out automatically.
#erl_xcomp_bigendian=
+# * `erl_xcomp_double_middle_endian` - `yes|no`. No default. If `yes`, the
+# target system must have doubles in "middle-endian" format. If
+# `no`, it has "regular" endianness. This can often be automatically
+# detected, but not always. If not automatically detected,
+# `configure` will fail unless this variable is set. Since no
+# default value is used, `configure` will try to figure this out
+# automatically.
+#erl_xcomp_double_middle_endian
+
# * `erl_xcomp_clock_gettime_cpu_time' - `yes|no'. Defaults to `no'. If `yes',
# the target system must have a working `clock_gettime()' implementation
# that can be used for retrieving process CPU time.
diff --git a/xcomp/erl-xcomp-avr32-atmel-linux-gnu.conf b/xcomp/erl-xcomp-avr32-atmel-linux-gnu.conf
index be22d498d6..e86043f5c8 100644
--- a/xcomp/erl-xcomp-avr32-atmel-linux-gnu.conf
+++ b/xcomp/erl-xcomp-avr32-atmel-linux-gnu.conf
@@ -190,6 +190,15 @@ erl_xcomp_sysroot="$ATMEL_SYSROOT_AVR32"
# value is used, `configure' will try to figure this out automatically.
#erl_xcomp_bigendian=
+# * `erl_xcomp_double_middle_endian` - `yes|no`. No default. If `yes`, the
+# target system must have doubles in "middle-endian" format. If
+# `no`, it has "regular" endianness. This can often be automatically
+# detected, but not always. If not automatically detected,
+# `configure` will fail unless this variable is set. Since no
+# default value is used, `configure` will try to figure this out
+# automatically.
+#erl_xcomp_double_middle_endian
+
# * `erl_xcomp_clock_gettime_cpu_time' - `yes|no'. Defaults to `no'. If `yes',
# the target system must have a working `clock_gettime()' implementation
# that can be used for retrieving process CPU time.
diff --git a/xcomp/erl-xcomp-mips-linux.conf b/xcomp/erl-xcomp-mips-linux.conf
index 3ac057bb4c..2195b7d6e9 100644
--- a/xcomp/erl-xcomp-mips-linux.conf
+++ b/xcomp/erl-xcomp-mips-linux.conf
@@ -187,6 +187,15 @@ CFLAGS=-Os
# value is used, `configure' will try to figure this out automatically.
#erl_xcomp_bigendian=
+# * `erl_xcomp_double_middle_endian` - `yes|no`. No default. If `yes`, the
+# target system must have doubles in "middle-endian" format. If
+# `no`, it has "regular" endianness. This can often be automatically
+# detected, but not always. If not automatically detected,
+# `configure` will fail unless this variable is set. Since no
+# default value is used, `configure` will try to figure this out
+# automatically.
+#erl_xcomp_double_middle_endian
+
# * `erl_xcomp_clock_gettime_cpu_time' - `yes|no'. Defaults to `no'. If `yes',
# the target system must have a working `clock_gettime()' implementation
# that can be used for retrieving process CPU time.
diff --git a/xcomp/erl-xcomp-vars.sh b/xcomp/erl-xcomp-vars.sh
index d9972eb3db..168b25262a 100644
--- a/xcomp/erl-xcomp-vars.sh
+++ b/xcomp/erl-xcomp-vars.sh
@@ -26,4 +26,4 @@
# and precious variables in $ERL_TOP/erts/aclocal.m4.
#
-erl_xcomp_vars="erl_xcomp_sysroot erl_xcomp_isysroot erl_xcomp_bigendian erl_xcomp_linux_clock_gettime_correction erl_xcomp_linux_nptl erl_xcomp_linux_usable_sigusrx erl_xcomp_linux_usable_sigaltstack erl_xcomp_poll erl_xcomp_kqueue erl_xcomp_putenv_copy erl_xcomp_reliable_fpe erl_xcomp_getaddrinfo erl_xcomp_gethrvtime_procfs_ioctl erl_xcomp_clock_gettime_cpu_time erl_xcomp_after_morecore_hook erl_xcomp_dlsym_brk_wrappers"
+erl_xcomp_vars="erl_xcomp_sysroot erl_xcomp_isysroot erl_xcomp_bigendian erl_xcomp_double_middle_endian erl_xcomp_linux_clock_gettime_correction erl_xcomp_linux_nptl erl_xcomp_linux_usable_sigusrx erl_xcomp_linux_usable_sigaltstack erl_xcomp_poll erl_xcomp_kqueue erl_xcomp_putenv_copy erl_xcomp_reliable_fpe erl_xcomp_getaddrinfo erl_xcomp_gethrvtime_procfs_ioctl erl_xcomp_clock_gettime_cpu_time erl_xcomp_after_morecore_hook erl_xcomp_dlsym_brk_wrappers"
diff --git a/xcomp/erl-xcomp-vxworks_ppc32.conf b/xcomp/erl-xcomp-vxworks_ppc32.conf
index ed8305ea93..bea4b3e374 100644
--- a/xcomp/erl-xcomp-vxworks_ppc32.conf
+++ b/xcomp/erl-xcomp-vxworks_ppc32.conf
@@ -187,6 +187,15 @@ AR="$WIND_BASE/gnu/3.4.4-vxworks-6.3/$WIND_HOST_TYPE/bin/arppc"
# value is used, `configure' will try to figure this out automatically.
erl_xcomp_bigendian=yes
+# * `erl_xcomp_double_middle_endian` - `yes|no`. No default. If `yes`, the
+# target system must have doubles in "middle-endian" format. If
+# `no`, it has "regular" endianness. This can often be automatically
+# detected, but not always. If not automatically detected,
+# `configure` will fail unless this variable is set. Since no
+# default value is used, `configure` will try to figure this out
+# automatically.
+#erl_xcomp_double_middle_endian
+
# * `erl_xcomp_clock_gettime_cpu_time' - `yes|no'. Defaults to `no'. If `yes',
# the target system must have a working `clock_gettime()' implementation
# that can be used for retrieving process CPU time.
diff --git a/xcomp/erl-xcomp-x86_64-saf-linux-gnu.conf b/xcomp/erl-xcomp-x86_64-saf-linux-gnu.conf
index f645ddbd96..de9118ae30 100644
--- a/xcomp/erl-xcomp-x86_64-saf-linux-gnu.conf
+++ b/xcomp/erl-xcomp-x86_64-saf-linux-gnu.conf
@@ -187,6 +187,15 @@ erl_xcomp_after_morecore_hook=yes
# value is used, `configure' will try to figure this out automatically.
#erl_xcomp_bigendian=
+# * `erl_xcomp_double_middle_endian` - `yes|no`. No default. If `yes`, the
+# target system must have doubles in "middle-endian" format. If
+# `no`, it has "regular" endianness. This can often be automatically
+# detected, but not always. If not automatically detected,
+# `configure` will fail unless this variable is set. Since no
+# default value is used, `configure` will try to figure this out
+# automatically.
+#erl_xcomp_double_middle_endian
+
# * `erl_xcomp_clock_gettime_cpu_time' - `yes|no'. Defaults to `no'. If `yes',
# the target system must have a working `clock_gettime()' implementation
# that can be used for retrieving process CPU time.
diff --git a/xcomp/erl-xcomp.conf.template b/xcomp/erl-xcomp.conf.template
index eb331ffe92..c215f0fb13 100644
--- a/xcomp/erl-xcomp.conf.template
+++ b/xcomp/erl-xcomp.conf.template
@@ -187,6 +187,15 @@
# value is used, `configure' will try to figure this out automatically.
#erl_xcomp_bigendian=
+# * `erl_xcomp_double_middle` - `yes|no`. No default. If `yes`, the
+# target system must have doubles in "middle-endian" format. If
+# `no`, it has "regular" endianness. This can often be automatically
+# detected, but not always. If not automatically detected,
+# `configure` will fail unless this variable is set. Since no
+# default value is used, `configure` will try to figure this out
+# automatically.
+#erl_xcomp_double_middle_endian
+
# * `erl_xcomp_clock_gettime_cpu_time' - `yes|no'. Defaults to `no'. If `yes',
# the target system must have a working `clock_gettime()' implementation
# that can be used for retrieving process CPU time.