aboutsummaryrefslogtreecommitdiffstats
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/crypto/c_src/Makefile.in8
-rw-r--r--lib/crypto/doc/src/licenses.xml2
-rw-r--r--lib/edoc/src/edoc.hrl2
-rw-r--r--lib/edoc/src/edoc_specs.erl6
-rw-r--r--lib/edoc/src/edoc_tags.erl116
-rw-r--r--lib/erl_docgen/priv/bin/specs_gen.escript101
-rw-r--r--lib/erl_docgen/priv/xsl/db_html.xsl556
-rw-r--r--lib/erl_docgen/priv/xsl/db_man.xsl424
-rw-r--r--lib/erl_docgen/priv/xsl/db_pdf.xsl451
-rw-r--r--lib/erl_docgen/src/otp_specs.erl14
-rw-r--r--lib/erl_interface/src/epmd/epmd_port.c7
-rw-r--r--lib/kernel/src/inet_res.erl4
-rw-r--r--lib/mnesia/doc/src/Mnesia_chap7.xmlsrc7
-rw-r--r--lib/mnesia/doc/src/mnesia.xml32
-rw-r--r--lib/mnesia/src/mnesia.erl11
-rw-r--r--lib/mnesia/src/mnesia.hrl1
-rw-r--r--lib/mnesia/src/mnesia_controller.erl26
-rw-r--r--lib/mnesia/src/mnesia_dumper.erl8
-rw-r--r--lib/mnesia/src/mnesia_lib.erl10
-rw-r--r--lib/mnesia/src/mnesia_loader.erl2
-rw-r--r--lib/mnesia/src/mnesia_locker.erl69
-rw-r--r--lib/mnesia/src/mnesia_schema.erl54
-rw-r--r--lib/mnesia/src/mnesia_tm.erl66
-rw-r--r--lib/mnesia/test/Makefile1
-rw-r--r--lib/mnesia/test/mnesia_SUITE.erl99
-rw-r--r--lib/mnesia/test/mnesia_majority_test.erl188
-rw-r--r--lib/public_key/doc/src/Makefile3
-rw-r--r--lib/public_key/doc/src/part.xml3
-rw-r--r--lib/public_key/doc/src/public_key.xml24
-rw-r--r--lib/public_key/doc/src/using_public_key.xml504
-rw-r--r--lib/reltool/src/reltool_server.erl189
-rw-r--r--lib/reltool/src/reltool_sys_win.erl26
-rw-r--r--lib/reltool/test/Makefile4
-rw-r--r--lib/reltool/test/reltool_server_SUITE.erl125
-rw-r--r--lib/reltool/test/reltool_server_SUITE_data/Makefile.src19
-rw-r--r--lib/reltool/test/reltool_server_SUITE_data/otp_9229/x-1.0/ebin/x.app7
-rw-r--r--lib/reltool/test/reltool_server_SUITE_data/otp_9229/x-1.0/src/mylib.erl4
-rw-r--r--lib/reltool/test/reltool_server_SUITE_data/otp_9229/x-1.0/src/x.erl4
-rw-r--r--lib/reltool/test/reltool_server_SUITE_data/otp_9229/y-1.0/ebin/y.app7
-rw-r--r--lib/reltool/test/reltool_server_SUITE_data/otp_9229/y-1.0/src/mylib.erl4
-rw-r--r--lib/reltool/test/reltool_server_SUITE_data/otp_9229/y-1.0/src/y.erl4
-rw-r--r--lib/sasl/Makefile4
-rw-r--r--lib/sasl/examples/ebin/.gitignore0
-rw-r--r--lib/sasl/examples/src/Makefile78
-rw-r--r--lib/sasl/examples/src/target_system.erl259
-rw-r--r--lib/sasl/src/rb.erl14
-rw-r--r--lib/sasl/test/.gitignore5
-rw-r--r--lib/sasl/test/Makefile91
-rw-r--r--lib/sasl/test/alarm_handler_SUITE.erl179
-rw-r--r--lib/sasl/test/installer.erl778
-rw-r--r--lib/sasl/test/overload_SUITE.erl175
-rw-r--r--lib/sasl/test/rb_SUITE.erl606
-rw-r--r--lib/sasl/test/release_handler_SUITE.erl1651
-rw-r--r--lib/sasl/test/release_handler_SUITE_data/Makefile.src108
-rw-r--r--lib/sasl/test/release_handler_SUITE_data/P2B/a-2.0/ebin/a.app8
-rw-r--r--lib/sasl/test/release_handler_SUITE_data/P2B/a-2.0/src/a.erl47
-rw-r--r--lib/sasl/test/release_handler_SUITE_data/P2B/a-2.0/src/a_sup.erl37
-rw-r--r--lib/sasl/test/release_handler_SUITE_data/app1_app2/lib1/app1-1.0/ebin/app1.app9
-rw-r--r--lib/sasl/test/release_handler_SUITE_data/app1_app2/lib1/app1-1.0/src/app1.erl22
-rw-r--r--lib/sasl/test/release_handler_SUITE_data/app1_app2/lib1/app1-1.0/src/app1_server.erl32
-rw-r--r--lib/sasl/test/release_handler_SUITE_data/app1_app2/lib1/app1-1.0/src/app1_sup.erl17
-rw-r--r--lib/sasl/test/release_handler_SUITE_data/app1_app2/lib1/app2-1.0/ebin/app2.app9
-rw-r--r--lib/sasl/test/release_handler_SUITE_data/app1_app2/lib1/app2-1.0/src/app2.erl17
-rw-r--r--lib/sasl/test/release_handler_SUITE_data/app1_app2/lib1/app2-1.0/src/app2_server.erl32
-rw-r--r--lib/sasl/test/release_handler_SUITE_data/app1_app2/lib1/app2-1.0/src/app2_sup.erl17
-rw-r--r--lib/sasl/test/release_handler_SUITE_data/app1_app2/lib2/app1-2.0/ebin/app1.app9
-rw-r--r--lib/sasl/test/release_handler_SUITE_data/app1_app2/lib2/app1-2.0/ebin/app1.appup4
-rw-r--r--lib/sasl/test/release_handler_SUITE_data/app1_app2/lib2/app1-2.0/src/app1.erl22
-rw-r--r--lib/sasl/test/release_handler_SUITE_data/app1_app2/lib2/app1-2.0/src/app1_server.erl35
-rw-r--r--lib/sasl/test/release_handler_SUITE_data/app1_app2/lib2/app1-2.0/src/app1_sup.erl17
-rw-r--r--lib/sasl/test/release_handler_SUITE_data/app1_app2/lib2/app2-1.0/ebin/app2.app9
-rw-r--r--lib/sasl/test/release_handler_SUITE_data/app1_app2/lib2/app2-1.0/src/app2.erl17
-rw-r--r--lib/sasl/test/release_handler_SUITE_data/app1_app2/lib2/app2-1.0/src/app2_server.erl32
-rw-r--r--lib/sasl/test/release_handler_SUITE_data/app1_app2/lib2/app2-1.0/src/app2_sup.erl17
-rw-r--r--lib/sasl/test/release_handler_SUITE_data/c/aa.erl41
-rw-r--r--lib/sasl/test/release_handler_SUITE_data/c/b.erl38
-rw-r--r--lib/sasl/test/release_handler_SUITE_data/c/c.app8
-rw-r--r--lib/sasl/test/release_handler_SUITE_data/c/c_sup.erl40
-rwxr-xr-xlib/sasl/test/release_handler_SUITE_data/clients/start_cli138
-rwxr-xr-xlib/sasl/test/release_handler_SUITE_data/clients/start_cli237
-rw-r--r--lib/sasl/test/release_handler_SUITE_data/lib/a-1.0/ebin/a.app8
-rw-r--r--lib/sasl/test/release_handler_SUITE_data/lib/a-1.0/src/a.app8
-rw-r--r--lib/sasl/test/release_handler_SUITE_data/lib/a-1.0/src/a.erl49
-rw-r--r--lib/sasl/test/release_handler_SUITE_data/lib/a-1.0/src/a_sup.erl37
-rw-r--r--lib/sasl/test/release_handler_SUITE_data/lib/a-1.1/ebin/a.app8
-rw-r--r--lib/sasl/test/release_handler_SUITE_data/lib/a-1.1/ebin/a.appup3
-rw-r--r--lib/sasl/test/release_handler_SUITE_data/lib/a-1.1/src/a.erl54
-rw-r--r--lib/sasl/test/release_handler_SUITE_data/lib/a-1.1/src/a_sup.erl37
-rw-r--r--lib/sasl/test/release_handler_SUITE_data/lib/installer-1.0/ebin/installer.app6
l---------lib/sasl/test/release_handler_SUITE_data/lib/installer-1.0/src/installer.erl1
-rw-r--r--lib/sasl/test/release_handler_SUITE_data/otp_2740/vsn_atom.erl26
-rw-r--r--lib/sasl/test/release_handler_SUITE_data/otp_2740/vsn_list.erl26
-rw-r--r--lib/sasl/test/release_handler_SUITE_data/otp_2740/vsn_numeric.erl26
-rw-r--r--lib/sasl/test/release_handler_SUITE_data/otp_2740/vsn_string.erl26
-rw-r--r--lib/sasl/test/release_handler_SUITE_data/otp_2740/vsn_tuple.erl26
-rwxr-xr-xlib/sasl/test/release_handler_SUITE_data/start29
l---------lib/sasl/test/release_handler_SUITE_data/target_system.erl1
-rw-r--r--lib/sasl/test/sasl.cover2
-rw-r--r--lib/sasl/test/sasl.spec1
-rw-r--r--lib/sasl/test/sasl_SUITE.erl98
-rw-r--r--lib/sasl/test/systools_SUITE.erl2112
-rw-r--r--lib/sasl/test/systools_SUITE_data/d_bad_app_vsn/lib/db-2.1/ebin/db.app8
-rw-r--r--lib/sasl/test/systools_SUITE_data/d_bad_app_vsn/lib/db-2.1/ebin/db.appup20
-rw-r--r--lib/sasl/test/systools_SUITE_data/d_bad_app_vsn/lib/db-2.1/src/db1.erl2
-rw-r--r--lib/sasl/test/systools_SUITE_data/d_bad_app_vsn/lib/db-2.1/src/db2.erl2
-rw-r--r--lib/sasl/test/systools_SUITE_data/d_bad_app_vsn/lib/fe-3.1/ebin/fe.app8
-rw-r--r--lib/sasl/test/systools_SUITE_data/d_bad_app_vsn/lib/fe-3.1/ebin/fe.appup27
-rw-r--r--lib/sasl/test/systools_SUITE_data/d_bad_app_vsn/lib/fe-3.1/src/fe1.erl2
-rw-r--r--lib/sasl/test/systools_SUITE_data/d_bad_app_vsn/lib/fe-3.1/src/fe2.erl2
-rw-r--r--lib/sasl/test/systools_SUITE_data/d_bad_app_vsn/lib/fe-3.1/src/fe3.erl2
-rw-r--r--lib/sasl/test/systools_SUITE_data/d_bad_appup/lib/fe-3.1/ebin/fe.app7
-rw-r--r--lib/sasl/test/systools_SUITE_data/d_bad_appup/lib/fe-3.1/ebin/fe.appup27
-rw-r--r--lib/sasl/test/systools_SUITE_data/d_bad_mod+warn/lib/db-2.1/ebin/db.app8
-rw-r--r--lib/sasl/test/systools_SUITE_data/d_bad_mod+warn/lib/db-2.1/ebin/db.appup20
-rw-r--r--lib/sasl/test/systools_SUITE_data/d_bad_mod+warn/lib/db-2.1/src/db1.erl2
-rw-r--r--lib/sasl/test/systools_SUITE_data/d_bad_mod+warn/lib/db-2.1/src/db2.erl2
-rw-r--r--lib/sasl/test/systools_SUITE_data/d_bad_mod+warn/lib/fe-3.1/ebin/fe.app8
-rw-r--r--lib/sasl/test/systools_SUITE_data/d_bad_mod+warn/lib/fe-3.1/ebin/fe.appup27
-rw-r--r--lib/sasl/test/systools_SUITE_data/d_bad_mod+warn/lib/fe-3.1/src/fe1.erl2
-rw-r--r--lib/sasl/test/systools_SUITE_data/d_bad_mod+warn/lib/fe-3.1/src/fe2.erl2
-rw-r--r--lib/sasl/test/systools_SUITE_data/d_bad_mod+warn/lib/fe-3.1/src/fe3.erl2
-rw-r--r--lib/sasl/test/systools_SUITE_data/d_links/lib/db-2.1/ebin/db.app8
-rw-r--r--lib/sasl/test/systools_SUITE_data/d_links/lib/db-2.1/ebin/db.appup20
-rw-r--r--lib/sasl/test/systools_SUITE_data/d_links/lib/db-2.1/src/db2.erl2
-rw-r--r--lib/sasl/test/systools_SUITE_data/d_links/lib/db-2.1/src/db3.erl2
-rw-r--r--lib/sasl/test/systools_SUITE_data/d_links/lib/fe-3.1/ebin/fe.appup27
-rw-r--r--lib/sasl/test/systools_SUITE_data/d_links/lib/fe-3.1/src/fe1.erl2
-rw-r--r--lib/sasl/test/systools_SUITE_data/d_links/lib/fe-3.1/src/fe2.erl2
-rw-r--r--lib/sasl/test/systools_SUITE_data/d_links/lib/fe-3.1/src/fe3.erl2
-rw-r--r--lib/sasl/test/systools_SUITE_data/d_missing_src/lib/db-2.1/ebin/db.app8
-rw-r--r--lib/sasl/test/systools_SUITE_data/d_missing_src/lib/db-2.1/ebin/db.appup20
-rw-r--r--lib/sasl/test/systools_SUITE_data/d_missing_src/lib/db-2.1/src/db1.erl2
-rw-r--r--lib/sasl/test/systools_SUITE_data/d_missing_src/lib/db-2.1/src/db2.erl2
-rw-r--r--lib/sasl/test/systools_SUITE_data/d_missing_src/lib/fe-3.1/ebin/fe.app8
-rw-r--r--lib/sasl/test/systools_SUITE_data/d_missing_src/lib/fe-3.1/ebin/fe.appup27
-rw-r--r--lib/sasl/test/systools_SUITE_data/d_missing_src/lib/fe-3.1/src/fe1.erl2
-rw-r--r--lib/sasl/test/systools_SUITE_data/d_missing_src/lib/fe-3.1/src/fe2.erl2
-rw-r--r--lib/sasl/test/systools_SUITE_data/d_missing_src/lib/fe-3.1/src/fe3.erl2
-rw-r--r--lib/sasl/test/systools_SUITE_data/d_no_appup/lib/fe-2.1/ebin/fe.app8
-rw-r--r--lib/sasl/test/systools_SUITE_data/d_no_appup/lib/fe-2.1/ebin/fe.appup27
-rw-r--r--lib/sasl/test/systools_SUITE_data/d_no_appup/lib/fe-3.1/ebin/fe.app7
-rw-r--r--lib/sasl/test/systools_SUITE_data/d_normal/lib/db-1.0/ebin/db.app8
-rw-r--r--lib/sasl/test/systools_SUITE_data/d_normal/lib/db-1.0/src/db1.erl2
-rw-r--r--lib/sasl/test/systools_SUITE_data/d_normal/lib/db-1.0/src/db2.erl2
-rw-r--r--lib/sasl/test/systools_SUITE_data/d_normal/lib/db-1.1/ebin/db.app8
-rw-r--r--lib/sasl/test/systools_SUITE_data/d_normal/lib/db-1.1/src/db1.erl2
-rw-r--r--lib/sasl/test/systools_SUITE_data/d_normal/lib/db-1.1/src/db2.erl2
-rw-r--r--lib/sasl/test/systools_SUITE_data/d_normal/lib/db-2.1/ebin/db.app7
-rw-r--r--lib/sasl/test/systools_SUITE_data/d_normal/lib/db-2.1/ebin/db.appup20
-rw-r--r--lib/sasl/test/systools_SUITE_data/d_normal/lib/db-2.1/src/db1.erl13
-rw-r--r--lib/sasl/test/systools_SUITE_data/d_normal/lib/db-2.1/src/db2.erl2
-rw-r--r--lib/sasl/test/systools_SUITE_data/d_normal/lib/db-3.1/ebin/db.app7
-rw-r--r--lib/sasl/test/systools_SUITE_data/d_normal/lib/db-3.1/ebin/db.appup20
-rw-r--r--lib/sasl/test/systools_SUITE_data/d_normal/lib/db-3.1/src/db1.erl2
-rw-r--r--lib/sasl/test/systools_SUITE_data/d_normal/lib/db-3.1/src/db2.erl2
-rw-r--r--lib/sasl/test/systools_SUITE_data/d_normal/lib/fe-2.1.1/ebin/fe.app8
-rw-r--r--lib/sasl/test/systools_SUITE_data/d_normal/lib/fe-2.1.1/src/fe1.erl2
-rw-r--r--lib/sasl/test/systools_SUITE_data/d_normal/lib/fe-2.1.1/src/fe2.erl2
-rw-r--r--lib/sasl/test/systools_SUITE_data/d_normal/lib/fe-2.1.1/src/fe3.erl2
-rw-r--r--lib/sasl/test/systools_SUITE_data/d_normal/lib/fe-2.1/ebin/fe.app8
-rw-r--r--lib/sasl/test/systools_SUITE_data/d_normal/lib/fe-2.1/src/fe1.erl2
-rw-r--r--lib/sasl/test/systools_SUITE_data/d_normal/lib/fe-2.1/src/fe2.erl2
-rw-r--r--lib/sasl/test/systools_SUITE_data/d_normal/lib/fe-2.1/src/fe3.erl2
-rw-r--r--lib/sasl/test/systools_SUITE_data/d_normal/lib/fe-3.1/ebin/fe.app7
-rw-r--r--lib/sasl/test/systools_SUITE_data/d_normal/lib/fe-3.1/ebin/fe.appup27
-rw-r--r--lib/sasl/test/systools_SUITE_data/d_normal/lib/fe-3.1/src/fe1.erl7
-rw-r--r--lib/sasl/test/systools_SUITE_data/d_normal/lib/fe-3.1/src/fe2.erl2
-rw-r--r--lib/sasl/test/systools_SUITE_data/d_normal/lib/fe-3.1/src/fe3.erl2
-rw-r--r--lib/sasl/test/systools_SUITE_data/lib/kernel/ebin/kernel.app6
-rw-r--r--lib/sasl/test/systools_SUITE_data/lib/kernel/ebin/kernel.appup12
-rw-r--r--lib/sasl/test/systools_SUITE_data/lib/stdlib/ebin/stdlib.app6
-rw-r--r--lib/sasl/test/systools_SUITE_data/lib/stdlib/ebin/stdlib.appup12
-rw-r--r--lib/sasl/test/systools_rc_SUITE.erl488
-rw-r--r--lib/snmp/src/manager/snmpm.erl20
-rw-r--r--lib/ssl/c_src/Makefile.in8
-rw-r--r--lib/ssl/src/ssl_connection.erl18
-rw-r--r--lib/stdlib/doc/src/re.xml2
-rw-r--r--lib/tools/src/cover.erl1
-rw-r--r--lib/tools/test/cover_SUITE.erl4
-rw-r--r--lib/xmerl/src/xmerl_sax_parser.erl4
-rw-r--r--lib/xmerl/vsn.mk2
181 files changed, 10752 insertions, 696 deletions
diff --git a/lib/crypto/c_src/Makefile.in b/lib/crypto/c_src/Makefile.in
index 040adcfd09..3ace10403e 100644
--- a/lib/crypto/c_src/Makefile.in
+++ b/lib/crypto/c_src/Makefile.in
@@ -40,7 +40,7 @@ CFLAGS = $(DED_CFLAGS)
# From erts/configure
SSL_LIBDIR = @SSL_LIBDIR@
SSL_INCLUDE = @SSL_INCLUDE@
-
+SSL_CRYPTO_LIBNAME = @SSL_CRYPTO_LIBNAME@
INCLUDES = $(SSL_INCLUDE) $(DED_INCLUDES)
@@ -84,10 +84,10 @@ DYNAMIC_CRYPTO_LIB=@SSL_DYNAMIC_ONLY@
ifeq ($(DYNAMIC_CRYPTO_LIB),yes)
SSL_DED_LD_RUNTIME_LIBRARY_PATH = @SSL_DED_LD_RUNTIME_LIBRARY_PATH@
-CRYPTO_LINK_LIB=$(SSL_DED_LD_RUNTIME_LIBRARY_PATH) -L$(SSL_LIBDIR) -lcrypto
+CRYPTO_LINK_LIB=$(SSL_DED_LD_RUNTIME_LIBRARY_PATH) -L$(SSL_LIBDIR) -l$(SSL_CRYPTO_LIBNAME)
else
SSL_DED_LD_RUNTIME_LIBRARY_PATH=
-CRYPTO_LINK_LIB=$(SSL_LIBDIR)/libcrypto.a
+CRYPTO_LINK_LIB=$(SSL_LIBDIR)/lib$(SSL_CRYPTO_LIBNAME).a
endif
# ----------------------------------------------------
@@ -112,7 +112,7 @@ $(LIBDIR)/crypto$(TYPEMARKER).so: $(OBJS)
$(LIBDIR)/crypto$(TYPEMARKER).dll: $(OBJS)
$(INSTALL_DIR) $(LIBDIR)
- $(LD) $(LDFLAGS) -o $@ $(SSL_DED_LD_RUNTIME_LIBRARY_PATH) -L$(SSL_LIBDIR) $(OBJS) -llibeay32
+ $(LD) $(LDFLAGS) -o $@ $(SSL_DED_LD_RUNTIME_LIBRARY_PATH) -L$(SSL_LIBDIR) $(OBJS) -l$(SSL_CRYPTO_LIBNAME)
clean:
ifeq ($(findstring win32,$(TARGET)), win32)
diff --git a/lib/crypto/doc/src/licenses.xml b/lib/crypto/doc/src/licenses.xml
index bae87a373e..e851655aa5 100644
--- a/lib/crypto/doc/src/licenses.xml
+++ b/lib/crypto/doc/src/licenses.xml
@@ -37,7 +37,7 @@ This chapter contains in extenso versions
<title>OpenSSL License</title>
<code type="none">
/* ====================================================================
- * Copyright (c) 1998-2002 The OpenSSL Project. All rights reserved.
+ * Copyright (c) 1998-2011 The OpenSSL Project. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
diff --git a/lib/edoc/src/edoc.hrl b/lib/edoc/src/edoc.hrl
index 43657b3b8f..31cf45ade9 100644
--- a/lib/edoc/src/edoc.hrl
+++ b/lib/edoc/src/edoc.hrl
@@ -37,7 +37,7 @@
-define(SOURCE_DIR, "src").
-define(EBIN_DIR, "ebin").
-define(EDOC_DIR, "doc").
--define(REPORT_MISSING_TYPE, false).
+-define(REPORT_MISSING_TYPES, false).
-include("edoc_doclet.hrl").
diff --git a/lib/edoc/src/edoc_specs.erl b/lib/edoc/src/edoc_specs.erl
index 45016ef85a..519ade726f 100644
--- a/lib/edoc/src/edoc_specs.erl
+++ b/lib/edoc/src/edoc_specs.erl
@@ -1,4 +1,4 @@
-%
+%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 1996-2011. All Rights Reserved.
@@ -428,8 +428,8 @@ get_typevars(Ts) ->
expand_records(Entries, TypeDefs, DT, Opts, File, Module) ->
TypeList = [{type_name(T), T, not_seen} || T <- TypeDefs],
true = ets:insert(DT, TypeList),
- Warn = proplists:get_value(report_missing_type, Opts,
- ?REPORT_MISSING_TYPE) =:= true,
+ Warn = proplists:get_value(report_missing_types, Opts,
+ ?REPORT_MISSING_TYPES) =:= true,
P = #parms{tab = DT, warn = Warn, file = File, line = 0},
ExportedTypes = [Name ||
{export_type,Ts} <- Module#module.attributes,
diff --git a/lib/edoc/src/edoc_tags.erl b/lib/edoc/src/edoc_tags.erl
index def39ee34c..8ee8f87b5f 100644
--- a/lib/edoc/src/edoc_tags.erl
+++ b/lib/edoc/src/edoc_tags.erl
@@ -331,8 +331,8 @@ parse_typedef(Data, Line, _Env, Where) ->
NAs = length(As),
case edoc_types:is_predefined(T, NAs) of
true ->
- case
- edoc_types:is_new_predefined(T, NAs)
+ case
+ edoc_types:is_new_predefined(T, NAs)
orelse edoc_types:is_predefined_otp_type(T, NAs)
of
false ->
@@ -406,17 +406,20 @@ throw_error(L, D) ->
-record(parms, {tab, warn, file, line}).
-check_types(Entries0, Opts, File) ->
- Entries = edoc_data:hidden_filter(Entries0, Opts),
+check_types(Entries, Opts, File) ->
Tags = edoc_data:get_all_tags(Entries),
+ TypeTags = [Tag || #tag{data = {#t_typedef{},_}}=Tag <- Tags],
+ Entries2 = edoc_data:hidden_filter(Entries, Opts),
+ Tags2 = edoc_data:get_all_tags(Entries2),
+ SpecTags = [Tag || #tag{data = #t_spec{}}=Tag <- Tags2],
DT = ets:new(types, [bag]),
_ = [add_type(DT, Name, As, File, Line) ||
#tag{line = Line,
- data = {#t_typedef{name = Name, args = As},_}} <- Tags],
- Warn = proplists:get_value(report_missing_type, Opts,
- ?REPORT_MISSING_TYPE) =:= true,
+ data = {#t_typedef{name = Name, args = As},_}} <- TypeTags],
+ Warn = proplists:get_value(report_missing_types, Opts,
+ ?REPORT_MISSING_TYPES) =:= true,
P = #parms{tab = DT, warn = Warn, file = File, line = 0},
- try check_types(Tags, P)
+ try check_types3(TypeTags++SpecTags, P, [])
after true = ets:delete(DT)
end.
@@ -431,60 +434,64 @@ add_type(DT, Name, Args, File, Line) ->
ets:insert(DT, {Name, NArgs})
end.
-check_types([], _P)->
+check_types3([], _P, _Ls)->
ok;
-check_types([Tag | Tags], P) ->
- check_type(Tag, P, Tags).
+check_types3([Tag | Tags], P, Ls) ->
+ check_type(Tag, P, Ls, Tags).
-check_type(#tag{line = L, data = Data}, P0, Ts) ->
+check_type(#tag{line = L, data = Data}, P0, Ls, Ts) ->
P = P0#parms{line = L},
case Data of
{#t_typedef{type = Type, defs = Defs},_} ->
- check_type(Type, P, Defs++Ts);
+ check_type(Type, P, Ls, Defs++Ts);
#t_spec{type = Type, defs = Defs} ->
- check_type(Type, P, Defs++Ts);
+ LocalTypes =
+ [{N,length(Args)} ||
+ #t_def{name = #t_type{name = N, args = Args}} <- Defs],
+ check_type(Type, P, LocalTypes, Defs),
+ check_types3(Ts, P, Ls);
_->
- check_types(Ts, P0)
+ check_types3(Ts, P0, Ls)
end;
-check_type(#t_def{type = Type}, P, Ts) ->
- check_type(Type, P, Ts);
-check_type(#t_type{name = Name, args = Args}, P, Ts) ->
- check_used_type(Name, Args, P),
- check_types(Args++Ts, P);
-check_type(#t_var{}, P, Ts) ->
- check_types(Ts, P);
-check_type(#t_fun{args = Args, range = Range}, P, Ts) ->
- check_type(Range, P, Args++Ts);
-check_type(#t_tuple{types = Types}, P, Ts) ->
- check_types(Types ++Ts, P);
-check_type(#t_list{type = Type}, P, Ts) ->
- check_type(Type, P, Ts);
-check_type(#t_nil{}, P, Ts) ->
- check_types(Ts, P);
-check_type(#t_paren{type = Type}, P, Ts) ->
- check_type(Type, P, Ts);
-check_type(#t_nonempty_list{type = Type}, P, Ts) ->
- check_type(Type, P, Ts);
-check_type(#t_atom{}, P, Ts) ->
- check_types(Ts, P);
-check_type(#t_integer{}, P, Ts) ->
- check_types(Ts, P);
-check_type(#t_integer_range{}, P, Ts) ->
- check_types(Ts, P);
-check_type(#t_binary{}, P, Ts) ->
- check_types(Ts, P);
-check_type(#t_float{}, P, Ts) ->
- check_types(Ts, P);
-check_type(#t_union{types = Types}, P, Ts) ->
- check_types(Types++Ts, P);
-check_type(#t_record{fields = Fields}, P, Ts) ->
- check_types(Fields++Ts, P);
-check_type(#t_field{type = Type}, P, Ts) ->
- check_type(Type, P, Ts);
-check_type(undefined, P, Ts) ->
- check_types(Ts, P).
-
-check_used_type(#t_name{name = N, module = Mod}=Name, Args, P) ->
+check_type(#t_def{type = Type}, P, Ls, Ts) ->
+ check_type(Type, P, Ls, Ts);
+check_type(#t_type{name = Name, args = Args}, P, Ls, Ts) ->
+ check_used_type(Name, Args, P, Ls),
+ check_types3(Args++Ts, P, Ls);
+check_type(#t_var{}, P, Ls, Ts) ->
+ check_types3(Ts, P, Ls);
+check_type(#t_fun{args = Args, range = Range}, P, Ls, Ts) ->
+ check_type(Range, P, Ls, Args++Ts);
+check_type(#t_tuple{types = Types}, P, Ls, Ts) ->
+ check_types3(Types ++Ts, P, Ls);
+check_type(#t_list{type = Type}, P, Ls, Ts) ->
+ check_type(Type, P, Ls, Ts);
+check_type(#t_nil{}, P, Ls, Ts) ->
+ check_types3(Ts, P, Ls);
+check_type(#t_paren{type = Type}, P, Ls, Ts) ->
+ check_type(Type, P, Ls, Ts);
+check_type(#t_nonempty_list{type = Type}, P, Ls, Ts) ->
+ check_type(Type, P, Ls, Ts);
+check_type(#t_atom{}, P, Ls, Ts) ->
+ check_types3(Ts, P, Ls);
+check_type(#t_integer{}, P, Ls, Ts) ->
+ check_types3(Ts, P, Ls);
+check_type(#t_integer_range{}, P, Ls, Ts) ->
+ check_types3(Ts, P, Ls);
+check_type(#t_binary{}, P, Ls, Ts) ->
+ check_types3(Ts, P, Ls);
+check_type(#t_float{}, P, Ls, Ts) ->
+ check_types3(Ts, P, Ls);
+check_type(#t_union{types = Types}, P, Ls, Ts) ->
+ check_types3(Types++Ts, P, Ls);
+check_type(#t_record{fields = Fields}, P, Ls, Ts) ->
+ check_types3(Fields++Ts, P, Ls);
+check_type(#t_field{type = Type}, P, Ls, Ts) ->
+ check_type(Type, P, Ls, Ts);
+check_type(undefined, P, Ls, Ts) ->
+ check_types3(Ts, P, Ls).
+
+check_used_type(#t_name{name = N, module = Mod}=Name, Args, P, LocalTypes) ->
NArgs = length(Args),
TypeName = {Name, NArgs},
DT = P#parms.tab,
@@ -493,6 +500,7 @@ check_used_type(#t_name{name = N, module = Mod}=Name, Args, P) ->
orelse lists:member(TypeName, ets:lookup(DT, Name))
orelse edoc_types:is_predefined(N, NArgs)
orelse edoc_types:is_predefined_otp_type(N, NArgs)
+ orelse lists:member(TypeName, LocalTypes)
of
true ->
ok;
diff --git a/lib/erl_docgen/priv/bin/specs_gen.escript b/lib/erl_docgen/priv/bin/specs_gen.escript
index 840fed6dd5..982afece7f 100644
--- a/lib/erl_docgen/priv/bin/specs_gen.escript
+++ b/lib/erl_docgen/priv/bin/specs_gen.escript
@@ -2,7 +2,7 @@
%% -*- erlang -*-
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2010. All Rights Reserved.
+%% Copyright Ericsson AB 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
@@ -29,7 +29,7 @@
%%% "-I<dir>" Directory to be searched when including a file.
%%% "-module Module"
%%% Module name to use when there is no File argument.
-%%% A temporary file will be created.
+%%% A empty specifications file will be created.
%%% Exactly one of -module Module and File must be given.
%%%
%%% The name of the generated file is "specs_<module>.xml". Its exact
@@ -67,34 +67,73 @@ usage() ->
halt(1).
call_edoc(FileSpec, InclFs, Dir) ->
- Incl = [{includes, InclFs}],
- Pre = [{preprocess, true}],
- Choice = [{dialyzer_specs, all}],
- DirOpt = [{dir, Dir}],
- Pretty = [{pretty_print, erl_pp}],
- Layout = [{layout, otp_specs},
- {file_suffix, ".specs"},
- {stylesheet, ""}],
- Warn = [{report_missing_type, false},
- {report_type_mismatch, false}],
- OptionList = (DirOpt ++ Choice ++ Pre ++ Warn ++ Pretty ++ Layout ++ Incl),
- {File, TmpFile} = case FileSpec of
- {file, File0} ->
- {File0, false};
- {module, Module} ->
- {create_tmp_file(Dir, Module), true}
- end,
- try edoc:files([File], OptionList) of
- ok ->
- clean_up(Dir, File, TmpFile),
- rename(Dir, File)
+ ReadOpts = [{includes, InclFs}, {preprocess, true}],
+ ExtractOpts = [{report_missing_type, false}],
+ LayoutOpts = [{pretty_printer, erl_pp}, {layout, otp_specs}],
+ File = case FileSpec of
+ {file, File0} -> File0;
+ {module, Module0} -> Module0
+ end,
+ try
+ Fs = case FileSpec of
+ {file, _} ->
+ Fs0 = read_file(File, ReadOpts),
+ clauses(Fs0);
+ {module, Module} ->
+ [{attribute,0,module,list_to_atom(Module)}]
+ end,
+ Doc = extract(File, Fs, ExtractOpts),
+ Text = edoc:layout(Doc, LayoutOpts),
+ ok = write_text(Text, File, Dir),
+ rename(Dir, File)
catch
_:_ ->
io:format("EDoc could not process file '~s'\n", [File]),
- clean_up(Dir, File, TmpFile),
+ clean_up(Dir),
halt(3)
end.
+read_file(File, Opts) ->
+ edoc:read_source(File, Opts).
+
+extract(File, Forms, Opts) ->
+ Env = edoc_lib:get_doc_env([], [], [], _Opts=[]),
+ {_Module, Doc} = edoc_extract:source(Forms, File, Env, Opts),
+ Doc.
+
+clauses(Fs) ->
+ clauses(Fs, no).
+
+clauses([], no) ->
+ [];
+clauses([F | Fs], Spec) ->
+ case F of
+ {attribute,_,spec,_} ->
+ clauses(Fs, F);
+ {function,_,_N,_A,_Cls} when Spec =/= no->
+ {attribute,_,spec,{Name,FunTypes}} = Spec,
+ %% [throw({no,Name,{_N,_A}}) || Name =/= {_N,_A}],
+ %% EDoc doesn't care if a function appears more than once;
+ %% this is how overloaded specs are handled:
+ (lists:append([[setelement(4, Spec, {Name,[T]}),F] ||
+ T <- FunTypes])
+ ++ clauses(Fs, no));
+ _ ->
+ [F | clauses(Fs, Spec)]
+ end.
+
+write_text(Text, File, Dir) ->
+ Base = filename:basename(File, ".erl"),
+ OutFile = filename:join(Dir, Base) ++ ".specs",
+ case file:write_file(OutFile, Text) of
+ ok ->
+ ok;
+ {error, R} ->
+ R1 = file:format_error(R),
+ io:format("could not write file '~s': ~s\n", [File, R1]),
+ halt(2)
+ end.
+
rename(Dir, F) ->
Mod = filename:basename(F, ".erl"),
Old = filename:join(Dir, Mod ++ ".specs"),
@@ -108,22 +147,10 @@ rename(Dir, F) ->
halt(2)
end.
-clean_up(Dir, File, TmpFile) ->
- [file:delete(File) || TmpFile],
+clean_up(Dir) ->
_ = [file:delete(filename:join(Dir, F)) ||
F <- ["packages-frame.html",
"overview-summary.html",
"modules-frame.html",
"index.html", "erlang.png", "edoc-info"]],
ok.
-
-create_tmp_file(Dir, Module) ->
- TmpFile = filename:join(Dir, Module++".erl"),
- case file:write_file(TmpFile, "-module(" ++ Module ++ ").\n") of
- ok ->
- TmpFile;
- {error, R} ->
- R1 = file:format_error(R),
- io:format("could not write file '~s': ~s\n", [TmpFile, R1]),
- halt(2)
- end.
diff --git a/lib/erl_docgen/priv/xsl/db_html.xsl b/lib/erl_docgen/priv/xsl/db_html.xsl
index c6375ea621..982572aeef 100644
--- a/lib/erl_docgen/priv/xsl/db_html.xsl
+++ b/lib/erl_docgen/priv/xsl/db_html.xsl
@@ -22,12 +22,15 @@
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+ xmlns:exsl="http://exslt.org/common"
+ extension-element-prefixes="exsl"
xmlns:fn="http://www.w3.org/2005/02/xpath-functions">
<xsl:include href="db_html_params.xsl"/>
<!-- Start of Dialyzer type/spec tags.
- See also the template matching "name" and the template "menu.funcs"
+ See also the templates matching "name" and "seealso" as well as
+ the template "menu.funcs"
-->
<xsl:param name="specs_file" select="''"/>
@@ -38,49 +41,58 @@
<xsl:key name="mod2app" match="module" use="@name"/>
<xsl:template name="err">
+ <xsl:param name="f"/>
<xsl:param name="m"/>
<xsl:param name="n"/>
<xsl:param name="a"/>
<xsl:param name="s"/>
<xsl:message terminate="yes">
- Error <xsl:if test="$m != ''"><xsl:value-of select ="$m"/>:</xsl:if>
- <xsl:value-of
- select="$n"/>/<xsl:value-of
- select="$a"/>: <xsl:value-of select="$s"/>
+ Error <xsl:if test="$f != ''">in <xsl:value-of select ="$f"/>:</xsl:if>
+ <xsl:if test="$m != ''"><xsl:value-of select ="$m"/>:</xsl:if>
+ <xsl:value-of select="$n"/>
+ <xsl:if test="$a != ''">/<xsl:value-of
+ select ="$a"/></xsl:if>: <xsl:value-of select="$s"/>
</xsl:message>
</xsl:template>
- <xsl:template name="spec_name">
+ <xsl:template name="find_spec">
<xsl:variable name="curModule" select="ancestor::erlref/module"/>
<xsl:variable name="mod" select="@mod"/>
<xsl:variable name="name" select="@name"/>
<xsl:variable name="arity" select="@arity"/>
- <xsl:variable name="clause" select="@clause"/>
+ <xsl:variable name="clause_i" select="@clause_i"/>
<xsl:variable name="spec0" select=
"$i/specs/module[@name=$curModule]/spec
[name=$name and arity=$arity
and (string-length($mod) = 0 or module = $mod)]"/>
- <xsl:variable name="spec" select="$spec0[string-length($clause) = 0
- or position() = $clause]"/>
- <xsl:if test="count($spec) = 0">
+ <xsl:variable name="spec" select="$spec0[string-length($clause_i) = 0
+ or position() = $clause_i]"/>
+
+ <xsl:if test="count($spec) != 1">
+ <xsl:variable name="why">
+ <xsl:choose>
+ <xsl:when test="count($spec) > 1">ambiguous spec</xsl:when>
+ <xsl:when test="count($spec) = 0">unknown spec</xsl:when>
+ </xsl:choose>
+ </xsl:variable>
<xsl:call-template name="err">
+ <xsl:with-param name="f" select="$curModule"/>
<xsl:with-param name="m" select="$mod"/>
<xsl:with-param name="n" select="$name"/>
<xsl:with-param name="a" select="$arity"/>
- <xsl:with-param name="s">unknown spec</xsl:with-param>
+ <xsl:with-param name="s" select="$why"/>
</xsl:call-template>
</xsl:if>
+ <xsl:copy-of select="$spec"/>
+ </xsl:template>
- <xsl:variable name="arity_clause">
- <xsl:choose>
- <xsl:when test="string-length(@clause) > 0">
- <xsl:value-of select="@arity"/>/<xsl:value-of select="@clause"/>
- </xsl:when>
- <xsl:otherwise>
- <xsl:value-of select="@arity"/>
- </xsl:otherwise>
- </xsl:choose>
+ <xsl:template name="spec_name">
+ <xsl:variable name="name" select="@name"/>
+ <xsl:variable name="arity" select="@arity"/>
+ <xsl:variable name="spec0">
+ <xsl:call-template name="find_spec"/>
</xsl:variable>
+ <xsl:variable name="spec" select="exsl:node-set($spec0)/spec"/>
<xsl:choose>
<xsl:when test="ancestor::cref">
@@ -89,72 +101,189 @@
</xsl:message>
</xsl:when>
<xsl:when test="ancestor::erlref">
- <a name="{$name}-{$arity_clause}"></a>
- <xsl:choose>
- <xsl:when test="string(@with_guards) = 'no'">
- <xsl:apply-templates select="$spec/contract/clause/head"/>
- </xsl:when>
- <xsl:otherwise>
- <xsl:call-template name="contract">
- <xsl:with-param name="contract" select="$spec/contract"/>
- </xsl:call-template>
- </xsl:otherwise>
+ <xsl:choose>
+ <xsl:when test="preceding-sibling::name[position() = 1
+ and @name = $name and @arity = $arity]">
+ <!-- Avoid duplicated anchors.-->
+ </xsl:when>
+ <xsl:otherwise>
+ <a name="{$name}-{$arity}"></a>
+ </xsl:otherwise>
</xsl:choose>
+
+ <xsl:variable name="global_types" select="ancestor::erlref/datatypes"/>
+ <xsl:variable name="local_types"
+ select="../type[string-length(@name) > 0]"/>
+ <xsl:apply-templates select="$spec/contract/clause/head">
+ <xsl:with-param name="local_types" select="$local_types"/>
+ <xsl:with-param name="global_types" select="$global_types"/>
+ </xsl:apply-templates>
</xsl:when>
</xsl:choose>
</xsl:template>
- <xsl:template name="contract">
- <xsl:param name="contract"/>
- <xsl:call-template name="clause">
- <xsl:with-param name="clause" select="$contract/clause"/>
- </xsl:call-template>
- </xsl:template>
-
- <xsl:template name="clause">
- <xsl:param name="clause"/>
- <xsl:variable name="type_desc" select="../type_desc"/>
- <xsl:for-each select="$clause">
- <xsl:apply-templates select="head"/>
- <xsl:if test="count(guard) > 0">
- <xsl:call-template name="guard">
- <xsl:with-param name="guard" select="guard"/>
- <xsl:with-param name="type_desc" select="$type_desc"/>
- </xsl:call-template>
- </xsl:if>
- </xsl:for-each>
- </xsl:template>
-
<xsl:template match="head">
+ <xsl:param name="local_types"/>
+ <xsl:param name="global_types"/>
<span class="bold_code">
- <xsl:apply-templates/>
+ <xsl:apply-templates mode="local_type">
+ <xsl:with-param name="local_types" select="$local_types"/>
+ <xsl:with-param name="global_types" select="$global_types"/>
+ </xsl:apply-templates>
</span>
<br/>
</xsl:template>
- <xsl:template name="guard">
- <xsl:param name="guard"/>
+ <!-- The *last* <name name="..." arity=".."/> -->
+ <xsl:template match="name" mode="types">
+ <xsl:variable name="name" select="@name"/>
+ <xsl:variable name="arity" select="@arity"/>
+ <xsl:variable name="spec0">
+ <xsl:call-template name="find_spec"/>
+ </xsl:variable>
+ <xsl:variable name="spec" select="exsl:node-set($spec0)/spec"/>
+ <xsl:variable name="clause" select="$spec/contract/clause"/>
+
+ <xsl:variable name="global_types" select="ancestor::erlref/datatypes"/>
+ <xsl:variable name="type_desc" select="../type_desc"/>
+ <!-- $type is data types to be presented as guards ("local types") -->
+ <xsl:variable name="type"
+ select="../type[string-length(@name) > 0
+ or string-length(@variable) > 0]"/>
+ <xsl:variable name="type_variables"
+ select ="$type[string-length(@variable) > 0]"/>
+ <xsl:variable name="local_types"
+ select ="$type[string-length(@name) > 0]"/>
+ <xsl:variable name="output_subtypes" select="count($type_variables) = 0"/>
+
+ <!-- It is assumed there is no support for overloaded specs
+ (there is no spec with more than one clause) -->
+ <xsl:if test="count($clause/guard) > 0 or count($type) > 0">
+ <div class="REFBODY"><p>Types:</p>
+
+ <xsl:choose>
+ <xsl:when test="$output_subtypes">
+ <xsl:call-template name="subtype">
+ <xsl:with-param name="subtype" select="$clause/guard/subtype"/>
+ <xsl:with-param name="type_desc" select="$type_desc"/>
+ <xsl:with-param name="local_types" select="$local_types"/>
+ <xsl:with-param name="global_types" select="$global_types"/>
+ </xsl:call-template>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:call-template name="type_variables">
+ <xsl:with-param name="type_variables" select="$type_variables"/>
+ <xsl:with-param name="type_desc" select="$type_desc"/>
+ <xsl:with-param name="local_types" select="$local_types"/>
+ <xsl:with-param name="global_types" select="$global_types"/>
+ <xsl:with-param name="fname" select="$name"/>
+ <xsl:with-param name="arity" select="$arity"/>
+ </xsl:call-template>
+
+ </xsl:otherwise>
+ </xsl:choose>
+
+ <xsl:call-template name="local_type">
+ <xsl:with-param name="type_desc" select="$type_desc"/>
+ <xsl:with-param name="local_types" select="$local_types"/>
+ <xsl:with-param name="global_types" select="$global_types"/>
+ </xsl:call-template>
+ </div>
+
+ </xsl:if>
+ </xsl:template>
+
+ <!-- Handle <type variable="..." name_i="..."/> -->
+ <xsl:template name="type_variables">
+ <xsl:param name="type_variables"/>
<xsl:param name="type_desc"/>
- <div class="REFBODY"><p>Types:</p>
+ <xsl:param name="local_types"/>
+ <xsl:param name="global_types"/>
+ <xsl:param name="fname"/>
+ <xsl:param name="arity"/>
+
+ <xsl:variable name="names" select="../name[string-length(@arity) > 0]"/>
+ <xsl:for-each select="$type_variables">
+ <xsl:variable name="name_i">
+ <xsl:choose>
+ <xsl:when test="string-length(@name_i) > 0">
+ <xsl:value-of select="@name_i"/>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:value-of select="count($names)"/>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:variable>
+ <xsl:variable name="spec0">
+ <xsl:for-each select="$names[position() = $name_i]">
+ <xsl:call-template name="find_spec"/>
+ </xsl:for-each>
+ </xsl:variable>
+ <xsl:variable name="spec" select="exsl:node-set($spec0)/spec"/>
+ <xsl:variable name="clause" select="$spec/contract/clause"/>
+ <xsl:variable name="variable" select="@variable"/>
+ <xsl:variable name="subtype"
+ select="$clause/guard/subtype[typename = $variable]"/>
+
+ <xsl:if test="count($subtype) = 0">
+ <xsl:call-template name="err">
+ <xsl:with-param name="f" select="ancestor::erlref/module"/>
+ <xsl:with-param name="n" select="$fname"/>
+ <xsl:with-param name="a" select="$arity"/>
+ <xsl:with-param name="s">unknown type variable <xsl:value-of select="$variable"/>
+ </xsl:with-param>
+ </xsl:call-template>
+ </xsl:if>
+
<xsl:call-template name="subtype">
- <xsl:with-param name="subtype" select="$guard/subtype"/>
- <xsl:with-param name="type_desc" select="$type_desc"/>
+ <xsl:with-param name="subtype" select="$subtype"/>
+ <xsl:with-param name="type_desc" select="$type_desc"/>
+ <xsl:with-param name="local_types" select="$local_types"/>
+ <xsl:with-param name="global_types" select="$global_types"/>
</xsl:call-template>
- </div>
+ </xsl:for-each>
</xsl:template>
<xsl:template name="subtype">
<xsl:param name="subtype"/>
<xsl:param name="type_desc"/>
+ <xsl:param name="local_types"/>
+ <xsl:param name="global_types"/>
+
<xsl:for-each select="$subtype">
<xsl:variable name="tname" select="typename"/>
- <xsl:variable name="tdesc" select="$type_desc[@name = $tname]"/>
<div class="REFTYPES">
<span class="bold_code">
- <xsl:apply-templates select="string"/>
+ <xsl:apply-templates select="string" mode="local_type">
+ <xsl:with-param name="local_types" select="$local_types"/>
+ <xsl:with-param name="global_types" select="$global_types"/>
+ </xsl:apply-templates>
</span>
</div>
- <xsl:apply-templates select="$type_desc[@name = $tname]"/>
+ <xsl:apply-templates select="$type_desc[@variable = $tname]"/>
+ </xsl:for-each>
+ </xsl:template>
+
+ <xsl:template name="local_type">
+ <xsl:param name="type_desc"/>
+ <xsl:param name="local_types"/>
+ <xsl:param name="global_types"/>
+
+ <xsl:for-each select="$local_types">
+ <div class="REFTYPES">
+ <xsl:call-template name="type_name">
+ <xsl:with-param name="mode" select="'local_type'"/>
+ <xsl:with-param name="local_types" select="$local_types"/>
+ <xsl:with-param name="global_types" select="$global_types"/>
+ </xsl:call-template>
+ </div>
+ <xsl:variable name="tname" select="@name"/>
+ <xsl:variable name="tnvars" select="@n_vars"/>
+ <xsl:apply-templates select=
+ "$type_desc[@name = $tname
+ and (@n_vars = $tnvars
+ or string-length(@n_vars) = 0 and
+ string-length($tnvars) = 0)]"/>
</xsl:for-each>
</xsl:template>
@@ -193,130 +322,175 @@
<xsl:apply-templates select="desc"/>
</xsl:template>
- <xsl:template match="typehead">
- <span class="bold_code">
- <xsl:apply-templates/>
- </span><br/>
- </xsl:template>
-
- <!-- local_defs -->
- <xsl:template match="local_defs">
- <div class="REFBODY">
- <xsl:apply-templates>
- </xsl:apply-templates>
- </div>
- </xsl:template>
-
- <xsl:template match="local_def">
- <div class="REFTYPES">
- <span class="bold_code">
- <xsl:apply-templates/>
- </span>
- </div>
- </xsl:template>
+ <!-- The "mode" attribute of apply has been used to separate the case
+ when datatypes are copied into specifications' subtypes.
+ A local type has no anchor. There are no links to local types
+ from local types or guards/head of the same specification.
+ -->
<xsl:template name="type_name">
+ <xsl:param name="mode"/> <!-- '' if <datatype> -->
+ <xsl:param name="local_types" select="/.."/>
+ <xsl:param name="global_types" select="/.."/>
<xsl:variable name="curModule" select="ancestor::erlref/module"/>
<xsl:variable name="mod" select="@mod"/>
<xsl:variable name="name" select="@name"/>
- <xsl:variable name="n_vars">
- <xsl:choose>
- <xsl:when test="string-length(@n_vars) > 0">
- <xsl:value-of select="@n_vars"/>
- </xsl:when>
- <xsl:otherwise>
- <xsl:value-of select="0"/>
- </xsl:otherwise>
- </xsl:choose>
- </xsl:variable>
+ <xsl:variable name="n_vars" select="@n_vars"/>
<xsl:choose>
<xsl:when test="string-length($name) > 0">
<xsl:variable name="type" select=
"$i/specs/module[@name=$curModule]/type
- [name=$name and n_vars=$n_vars
+ [name=$name
+ and (string-length($n_vars) = 0 or n_vars = $n_vars)
and (string-length($mod) = 0 or module = $mod)]"/>
<xsl:if test="count($type) != 1">
+ <xsl:variable name="why">
+ <xsl:choose>
+ <xsl:when test="count($type) > 1">ambiguous type</xsl:when>
+ <xsl:when test="count($type) = 0">unknown type</xsl:when>
+ </xsl:choose>
+ </xsl:variable>
<xsl:call-template name="err">
+ <xsl:with-param name="f" select="$curModule"/>
<xsl:with-param name="m" select="$mod"/>
<xsl:with-param name="n" select="$name"/>
<xsl:with-param name="a" select="$n_vars"/>
- <xsl:with-param name="s">unknown type</xsl:with-param>
+ <xsl:with-param name="s" select="$why"/>
</xsl:call-template>
</xsl:if>
- <xsl:apply-templates select="$type/typedecl"/>
+ <xsl:choose>
+ <xsl:when test="$mode = ''">
+ <xsl:apply-templates select="$type/typedecl"/>
+ </xsl:when>
+ <xsl:when test="$mode = 'local_type'">
+ <xsl:apply-templates select="$type/typedecl" mode="local_type">
+ <xsl:with-param name="local_types" select="$local_types"/>
+ <xsl:with-param name="global_types" select="$global_types"/>
+ </xsl:apply-templates>
+ </xsl:when>
+ </xsl:choose>
</xsl:when>
- <xsl:otherwise>
+ <xsl:otherwise> <!-- <datatype> with <name> -->
<span class="bold_code">
- <xsl:value-of select="."/>
+ <xsl:apply-templates/>
</span>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
+ <xsl:template match="typehead">
+ <span class="bold_code">
+ <xsl:apply-templates/>
+ </span><br/>
+ </xsl:template>
+
+ <xsl:template match="typehead" mode="local_type">
+ <xsl:param name="local_types"/>
+ <xsl:param name="global_types"/>
+ <span class="bold_code">
+ <xsl:apply-templates mode="local_type">
+ <xsl:with-param name="local_types" select="$local_types"/>
+ <xsl:with-param name="global_types" select="$global_types"/>
+ </xsl:apply-templates>
+ </span><br/>
+ </xsl:template>
+
+ <!-- Not used right now -->
+ <!-- local_defs -->
+ <xsl:template match="local_defs">
+ <div class="REFBODY">
+ <xsl:apply-templates>
+ </xsl:apply-templates>
+ </div>
+ </xsl:template>
+
+ <!-- Not used right now -->
+ <xsl:template match="local_def">
+ <div class="REFTYPES">
+ <span class="bold_code">
+ <xsl:apply-templates/>
+ </span>
+ </div>
+ </xsl:template>
+
<!-- Used both in <datatype> and in <func>! -->
<xsl:template match="anno">
<xsl:variable name="curModule" select="ancestor::erlref/module"/>
<xsl:variable name="anno" select="normalize-space(text())"/>
<xsl:variable name="namespec"
- select="ancestor::desc/preceding-sibling::name"/>
+ select="ancestor::type_desc/preceding-sibling::name
+ | ancestor::desc/preceding-sibling::name"/>
<xsl:if test="count($namespec) = 0 and string-length($specs_file) > 0">
<xsl:call-template name="err">
- <xsl:with-param name="s">cannot find 'name' (<xsl:value-of select="$anno"/>)
+ <xsl:with-param name="f" select="$curModule"/>
+ <xsl:with-param name="s">cannot find tag 'name' (anno <xsl:value-of select="$anno"/>)
</xsl:with-param>
</xsl:call-template>
</xsl:if>
- <xsl:variable name="mod" select="$namespec/@mod"/>
- <xsl:variable name="name" select="$namespec/@name"/>
- <xsl:variable name="arity" select="$namespec/@arity"/>
- <xsl:variable name="clause" select="$namespec/@clause"/>
- <xsl:variable name="tmp_n_vars" select="$namespec/@n_vars"/>
- <xsl:variable name="n_vars">
- <xsl:choose>
- <xsl:when test="string-length($tmp_n_vars) > 0">
- <xsl:value-of select="$tmp_n_vars"/>
- </xsl:when>
- <xsl:otherwise>
- <xsl:value-of select="0"/>
- </xsl:otherwise>
- </xsl:choose>
+ <!-- Search "local types" as well -->
+ <xsl:variable name="local_types"
+ select="ancestor::desc/preceding-sibling::type
+ [string-length(@name) > 0]"/>
+ <xsl:variable name="has_anno_in_local_type">
+ <xsl:for-each select="$local_types">
+ <xsl:call-template name="anno_name">
+ <xsl:with-param name="curModule" select="$curModule"/>
+ <xsl:with-param name="anno" select="$anno"/>
+ </xsl:call-template>
+ </xsl:for-each>
+ </xsl:variable>
+
+ <xsl:variable name="has_anno">
+ <xsl:for-each select="$namespec">
+ <xsl:call-template name="anno_name">
+ <xsl:with-param name="curModule" select="$curModule"/>
+ <xsl:with-param name="anno" select="$anno"/>
+ </xsl:call-template>
+ </xsl:for-each>
</xsl:variable>
+
+ <xsl:if test="$has_anno = '' and $has_anno_in_local_type = ''">
+ <xsl:call-template name="err">
+ <xsl:with-param name="f" select="$curModule"/>
+ <xsl:with-param name="m" select="$namespec/@mod"/>
+ <xsl:with-param name="n" select="$namespec/@name"/>
+ <xsl:with-param name="a" select="'-'"/>
+ <xsl:with-param name="s">unknown annotation <xsl:value-of select="$anno"/>
+ </xsl:with-param>
+ </xsl:call-template>
+ </xsl:if>
+ <xsl:value-of select="$anno"/>
+ </xsl:template>
+
+ <xsl:template name="anno_name">
+ <xsl:param name="curModule"/>
+ <xsl:param name="anno"/>
+ <xsl:variable name="mod" select="@mod"/>
+ <xsl:variable name="name" select="@name"/>
+ <xsl:variable name="arity" select="@arity"/>
+ <xsl:variable name="n_vars" select="@n_vars"/>
+ <xsl:variable name="clause_i" select="@clause_i"/>
+
<xsl:variable name="spec0" select=
"$i/specs/module[@name=$curModule]/spec
[name=$name and arity=$arity
and (string-length($mod) = 0 or module = $mod)]"/>
<xsl:variable name="spec_annos" select=
- "$spec0[string-length($clause) = 0
- or position() = $clause]/anno[.=$anno]"/>
+ "$spec0[string-length($clause_i) = 0
+ or position() = $clause_i]/anno[.=$anno]"/>
<xsl:variable name="type_annos" select=
"$i/specs/module[@name=$curModule]/type
- [name=$name and n_vars=$n_vars
+ [name=$name
+ and (string-length($n_vars) = 0 or n_vars=$n_vars)
and (string-length($mod) = 0 or module = $mod)]/anno[.=$anno]"/>
-
- <xsl:if test="count($spec_annos) = 0
- and count($type_annos) = 0
- and string-length($specs_file) > 0">
- <xsl:variable name="n">
- <xsl:choose>
- <xsl:when test="string-length($arity) = 0">
- <xsl:value-of select="$n_vars"/>
- </xsl:when>
- <xsl:otherwise>
- <xsl:value-of select="$arity"/>
- </xsl:otherwise>
- </xsl:choose>
- </xsl:variable>
- <xsl:call-template name="err">
- <xsl:with-param name="m" select="$mod"/>
- <xsl:with-param name="n" select="$name"/>
- <xsl:with-param name="a" select="$n"/>
- <xsl:with-param name="s">unknown annotation <xsl:value-of select="$anno"/>
- </xsl:with-param>
- </xsl:call-template>
+ <xsl:if test="count($spec_annos) != 0
+ or count($type_annos) != 0
+ or string-length($specs_file) = 0">
+ <xsl:value-of select="true()"/>
</xsl:if>
- <xsl:value-of select="$anno"/>
</xsl:template>
<!-- Used for indentation of formatted types and specs -->
@@ -324,6 +498,50 @@
<xsl:text>&#160;</xsl:text>
</xsl:template>
+ <xsl:template match="nbsp" mode="local_type">
+ <xsl:apply-templates select="."/>
+ </xsl:template>
+
+ <xsl:template match="br" mode="local_type">
+ <xsl:apply-templates select="."/>
+ </xsl:template>
+
+ <xsl:template match="marker" mode="local_type">
+ <xsl:param name="local_types"/>
+ <xsl:param name="global_types"/>
+ <!-- Craete no anchor -->
+ <!-- It would be possible to create a link to the global type
+ (if there is one), but that would mean even more code...
+ -->
+ <xsl:apply-templates/>
+ </xsl:template>
+
+ <!-- Does not look at @n_vars -->
+ <xsl:template match="seealso" mode="local_type">
+ <xsl:param name="local_types"/>
+ <xsl:param name="global_types"/>
+
+ <xsl:variable name="filepart"><xsl:value-of select="substring-before(@marker, '#')"/></xsl:variable>
+ <xsl:variable name="linkpart"><xsl:value-of select="translate(substring-after(@marker, '#'), '/', '-')"/></xsl:variable>
+
+ <xsl:choose>
+ <xsl:when test="string-length($filepart) > 0">
+ <xsl:call-template name="seealso"/>
+ </xsl:when>
+ <xsl:when test="count($local_types[concat('type-', @name) = $linkpart]) = 0">
+ <xsl:call-template name="seealso"/>
+ </xsl:when>
+ <xsl:when test="count($global_types/datatype/name[concat('type-', @name) = $linkpart]) > 0">
+ <!-- The type is both local and global; link to the global type -->
+ <xsl:call-template name="seealso"/>
+ </xsl:when>
+ <xsl:otherwise>
+ <!-- No link to local type -->
+ <xsl:apply-templates/>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:template>
+
<!-- End of Dialyzer type/spec tags -->
<!-- Page layout -->
@@ -1178,14 +1396,7 @@
<xsl:choose>
<xsl:when test="string-length(@arity) > 0">
<!-- Dialyzer spec -->
- <xsl:choose>
- <xsl:when test="string-length(@clause) > 0">
- <xsl:value-of select="@arity"/>/<xsl:value-of select="@clause"/>
- </xsl:when>
- <xsl:otherwise>
- <xsl:value-of select="@arity"/>
- </xsl:otherwise>
- </xsl:choose>
+ <xsl:value-of select="@arity"/>
</xsl:when>
<xsl:otherwise>
<xsl:call-template name="calc-arity">
@@ -1221,11 +1432,19 @@
</xsl:choose>
</xsl:variable>
- <li title="{$fname}-{$arity}">
- <a href="{$basename}.html#{$fname}-{$arity}">
- <xsl:value-of select="$fname"/>/<xsl:value-of select="$arity"/>
- </a>
- </li>
+ <xsl:choose>
+ <xsl:when test="preceding-sibling::name[position() = 1
+ and @name = $fname and @arity = $arity]">
+ <!-- Skip. Only works for Dialyzer specs. -->
+ </xsl:when>
+ <xsl:otherwise>
+ <li title="{$fname}-{$arity}">
+ <a href="{$basename}.html#{$fname}-{$arity}">
+ <xsl:value-of select="$fname"/>/<xsl:value-of select="$arity"/>
+ </a>
+ </li>
+ </xsl:otherwise>
+ </xsl:choose>
</xsl:when>
</xsl:choose>
@@ -1470,7 +1689,10 @@
<xsl:template match="func">
<xsl:param name="partnum"/>
- <p><xsl:apply-templates select="name"/></p>
+ <p><xsl:apply-templates select="name"/>
+ <xsl:apply-templates
+ select="name[string-length(@arity) > 0 and position()=last()]"
+ mode="types"/></p>
<xsl:apply-templates select="fsummary|type|desc">
<xsl:with-param name="partnum" select="$partnum"/>
@@ -1478,7 +1700,6 @@
</xsl:template>
-
<xsl:template match="name">
<xsl:choose>
<!-- @arity is mandatory when referring to a specification -->
@@ -1488,6 +1709,11 @@
<xsl:when test="ancestor::datatype">
<xsl:call-template name="type_name"/>
</xsl:when>
+ <xsl:when test="string-length(text()) = 0 and ancestor::erlref">
+ <xsl:message terminate="yes">
+ Error <xsl:value-of select="@name"/>: arity is mandatory when referring to specifications!
+ </xsl:message>
+ </xsl:when>
<xsl:otherwise>
<xsl:call-template name="name"/>
</xsl:otherwise>
@@ -1556,12 +1782,17 @@
<xsl:template match="type">
<xsl:param name="partnum"/>
- <div class="REFBODY"><p>Types:</p>
+ <!-- The case where @name != 0 is taken care of in "type_name" -->
+ <xsl:if test="string-length(@name) = 0 and string-length(@variable) = 0">
- <xsl:apply-templates>
- <xsl:with-param name="partnum" select="$partnum"/>
- </xsl:apply-templates>
- </div>
+ <div class="REFBODY"><p>Types:</p>
+
+ <xsl:apply-templates>
+ <xsl:with-param name="partnum" select="$partnum"/>
+ </xsl:apply-templates>
+ </div>
+
+ </xsl:if>
</xsl:template>
@@ -1612,7 +1843,10 @@
</xsl:template>
<xsl:template match="seealso">
+ <xsl:call-template name="seealso"/>
+ </xsl:template>
+ <xsl:template name="seealso">
<xsl:variable name="filepart"><xsl:value-of select="substring-before(@marker, '#')"/></xsl:variable>
<xsl:variable name="linkpart"><xsl:value-of select="translate(substring-after(@marker, '#'), '/', '-')"/></xsl:variable>
@@ -1633,16 +1867,16 @@
<xsl:variable name="app"
select="$m2a/mod2app/module[@name=$filepart]"/>
-->
- <xsl:variable name="reftext" select="text()"/>
+ <xsl:variable name="this" select="."/>
<xsl:for-each select="$m2a">
<xsl:variable name="app" select="key('mod2app', $filepart)"/>
<xsl:choose>
<xsl:when test="string-length($app) > 0">
- <span class="bold_code"><a href="javascript:erlhref('{$topdocdir}/../','{$app}','{$filepart}.html');"><xsl:value-of select="$reftext"/></a></span>
+ <span class="bold_code"><a href="javascript:erlhref('{$topdocdir}/../','{$app}','{$filepart}.html#{$linkpart}');"><xsl:value-of select="$this"/></a></span>
</xsl:when>
<xsl:otherwise>
<!-- Unknown application; no link -->
- <xsl:value-of select="$reftext"/>
+ <xsl:value-of select="$this"/>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
diff --git a/lib/erl_docgen/priv/xsl/db_man.xsl b/lib/erl_docgen/priv/xsl/db_man.xsl
index 2a8fb9fe3e..25b62f68c5 100644
--- a/lib/erl_docgen/priv/xsl/db_man.xsl
+++ b/lib/erl_docgen/priv/xsl/db_man.xsl
@@ -3,7 +3,7 @@
#
# %CopyrightBegin%
#
- # Copyright Ericsson AB 2009-2010. All Rights Reserved.
+ # Copyright Ericsson AB 2009-2011. All Rights Reserved.
#
# The contents of this file are subject to the Erlang Public License,
# Version 1.1, (the "License"); you may not use this file except in
@@ -21,13 +21,16 @@
-->
<xsl:stylesheet version="1.0"
- xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
+ xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+ xmlns:exsl="http://exslt.org/common"
+ extension-element-prefixes="exsl">
<xsl:preserve-space elements="code pre"/>
<xsl:strip-space elements="*"/>
<xsl:output method="text" encoding="UTF-8" indent="no"/>
- <!-- Start of Dialyzer type/spec tags. See also the template matching "name"
+ <!-- Start of Dialyzer type/spec tags. See also the templates
+ matching "name" and "seealso"
-->
<!-- Note: specs data for *one* module (as opposed to html and pdf) -->
@@ -35,38 +38,58 @@
<xsl:variable name="i" select="document($specs_file)"></xsl:variable>
<xsl:template name="err">
+ <xsl:param name="f"/>
<xsl:param name="m"/>
<xsl:param name="n"/>
<xsl:param name="a"/>
<xsl:param name="s"/>
<xsl:message terminate="yes">
- Error <xsl:if test="$m != ''"><xsl:value-of select ="$m"/>:</xsl:if>
- <xsl:value-of
- select="$n"/>/<xsl:value-of
- select="$a"/>: <xsl:value-of select="$s"/>
+ Error <xsl:if test="$f != ''">in <xsl:value-of select ="$f"/>:</xsl:if>
+ <xsl:if test="$m != ''"><xsl:value-of select ="$m"/>:</xsl:if>
+ <xsl:value-of select="$n"/>
+ <xsl:if test="$a != ''">/<xsl:value-of
+ select ="$a"/></xsl:if>: <xsl:value-of select="$s"/>
</xsl:message>
</xsl:template>
- <xsl:template name="spec_name">
+ <xsl:template name="find_spec">
<xsl:variable name="curModule" select="ancestor::erlref/module"/>
<xsl:variable name="mod" select="@mod"/>
<xsl:variable name="name" select="@name"/>
<xsl:variable name="arity" select="@arity"/>
- <xsl:variable name="clause" select="@clause"/>
+ <xsl:variable name="clause_i" select="@clause_i"/>
<xsl:variable name="spec0" select=
"$i/module[@name=$curModule]/spec
[name=$name and arity=$arity
and (string-length($mod) = 0 or module = $mod)]"/>
- <xsl:variable name="spec" select="$spec0[string-length($clause) = 0
- or position() = $clause]"/>
- <xsl:if test="count($spec) = 0">
+ <xsl:variable name="spec" select="$spec0[string-length($clause_i) = 0
+ or position() = $clause_i]"/>
+
+ <xsl:if test="count($spec) != 1">
+ <xsl:variable name="why">
+ <xsl:choose>
+ <xsl:when test="count($spec) > 1">ambiguous spec</xsl:when>
+ <xsl:when test="count($spec) = 0">unknown spec</xsl:when>
+ </xsl:choose>
+ </xsl:variable>
<xsl:call-template name="err">
+ <xsl:with-param name="f" select="$curModule"/>
<xsl:with-param name="m" select="$mod"/>
<xsl:with-param name="n" select="$name"/>
<xsl:with-param name="a" select="$arity"/>
- <xsl:with-param name="s">unknown spec</xsl:with-param>
+ <xsl:with-param name="s" select="$why"/>
</xsl:call-template>
</xsl:if>
+ <xsl:copy-of select="$spec"/>
+ </xsl:template>
+
+ <xsl:template name="spec_name">
+ <xsl:variable name="name" select="@name"/>
+ <xsl:variable name="arity" select="@arity"/>
+ <xsl:variable name="spec0">
+ <xsl:call-template name="find_spec"/>
+ </xsl:variable>
+ <xsl:variable name="spec" select="exsl:node-set($spec0)/spec"/>
<xsl:choose>
<xsl:when test="ancestor::cref">
@@ -75,42 +98,12 @@
</xsl:message>
</xsl:when>
<xsl:when test="ancestor::erlref">
- <xsl:choose>
- <xsl:when test="string(@with_guards) = 'no'">
- <xsl:apply-templates select="$spec/contract/clause/head"/>
- </xsl:when>
- <xsl:otherwise>
- <xsl:call-template name="contract">
- <xsl:with-param name="contract" select="$spec/contract"/>
- </xsl:call-template>
- </xsl:otherwise>
- </xsl:choose>
+ <xsl:apply-templates select="$spec/contract/clause/head"/>
<xsl:text>&#10;.br</xsl:text>
</xsl:when>
</xsl:choose>
</xsl:template>
- <xsl:template name="contract">
- <xsl:param name="contract"/>
- <xsl:call-template name="clause">
- <xsl:with-param name="clause" select="$contract/clause"/>
- </xsl:call-template>
- </xsl:template>
-
- <xsl:template name="clause">
- <xsl:param name="clause"/>
- <xsl:variable name="type_desc" select="../type_desc"/>
- <xsl:for-each select="$clause">
- <xsl:apply-templates select="head"/>
- <xsl:if test="count(guard) > 0">
- <xsl:call-template name="guard">
- <xsl:with-param name="guard" select="guard"/>
- <xsl:with-param name="type_desc" select="$type_desc"/>
- </xsl:call-template>
- </xsl:if>
- </xsl:for-each>
- </xsl:template>
-
<xsl:template match="head">
<xsl:text>&#10;.nf&#10;</xsl:text>
<xsl:text>&#10;.B&#10;</xsl:text>
@@ -119,29 +112,141 @@
<xsl:text>&#10;.fi</xsl:text>
</xsl:template>
- <xsl:template name="guard">
- <xsl:param name="guard"/>
+ <!-- The *last* <name name="..." arity=".."/> -->
+ <xsl:template match="name" mode="types">
+ <xsl:variable name="name" select="@name"/>
+ <xsl:variable name="arity" select="@arity"/>
+ <xsl:variable name="spec0">
+ <xsl:call-template name="find_spec"/>
+ </xsl:variable>
+ <xsl:variable name="spec" select="exsl:node-set($spec0)/spec"/>
+ <xsl:variable name="clause" select="$spec/contract/clause"/>
+
+ <xsl:variable name="type_desc" select="../type_desc"/>
+ <!-- $type is data types to be presented as guards ("local types") -->
+ <xsl:variable name="type"
+ select="../type[string-length(@name) > 0
+ or string-length(@variable) > 0]"/>
+ <xsl:variable name="type_variables"
+ select ="$type[string-length(@variable) > 0]"/>
+ <xsl:variable name="local_types"
+ select ="$type[string-length(@name) > 0]"/>
+ <xsl:variable name="output_subtypes" select="count($type_variables) = 0"/>
+
+ <!-- It is assumed there is no support for overloaded specs
+ (there is no spec with more than one clause) -->
+ <xsl:if test="count($clause/guard) > 0 or count($type) > 0">
+ <xsl:text>&#10;.RS</xsl:text>
+ <xsl:text>&#10;.TP</xsl:text>
+ <xsl:text>&#10;Types</xsl:text>
+
+ <xsl:choose>
+ <xsl:when test="$output_subtypes">
+ <xsl:call-template name="subtype">
+ <xsl:with-param name="subtype" select="$clause/guard/subtype"/>
+ <xsl:with-param name="type_desc" select="$type_desc"/>
+ <xsl:with-param name="local_types" select="$local_types"/>
+ </xsl:call-template>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:call-template name="type_variables">
+ <xsl:with-param name="type_variables" select="$type_variables"/>
+ <xsl:with-param name="type_desc" select="$type_desc"/>
+ <xsl:with-param name="local_types" select="$local_types"/>
+ <xsl:with-param name="fname" select="$name"/>
+ <xsl:with-param name="arity" select="$arity"/>
+ </xsl:call-template>
+
+ </xsl:otherwise>
+ </xsl:choose>
+
+ <xsl:call-template name="local_type">
+ <xsl:with-param name="type_desc" select="$type_desc"/>
+ <xsl:with-param name="local_types" select="$local_types"/>
+ </xsl:call-template>
+ <xsl:text>&#10;.RE</xsl:text>
+
+ </xsl:if>
+ </xsl:template>
+
+ <!-- Handle <type variable="..." name_i="..."/> -->
+ <xsl:template name="type_variables">
+ <xsl:param name="type_variables"/>
<xsl:param name="type_desc"/>
- <xsl:text>&#10;.RS</xsl:text>
- <xsl:text>&#10;.TP</xsl:text>
- <xsl:text>&#10;Types</xsl:text>
- <xsl:call-template name="subtype">
- <xsl:with-param name="subtype" select="$guard/subtype"/>
- <xsl:with-param name="type_desc" select="$type_desc"/>
- </xsl:call-template>
- <xsl:text>&#10;.RE</xsl:text>
+ <xsl:param name="local_types"/>
+ <xsl:param name="fname"/>
+ <xsl:param name="arity"/>
+
+ <xsl:variable name="names" select="../name[string-length(@arity) > 0]"/>
+ <xsl:for-each select="$type_variables">
+ <xsl:variable name="name_i">
+ <xsl:choose>
+ <xsl:when test="string-length(@name_i) > 0">
+ <xsl:value-of select="@name_i"/>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:value-of select="count($names)"/>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:variable>
+ <xsl:variable name="spec0">
+ <xsl:for-each select="$names[position() = $name_i]">
+ <xsl:call-template name="find_spec"/>
+ </xsl:for-each>
+ </xsl:variable>
+ <xsl:variable name="spec" select="exsl:node-set($spec0)/spec"/>
+ <xsl:variable name="clause" select="$spec/contract/clause"/>
+ <xsl:variable name="variable" select="@variable"/>
+ <xsl:variable name="subtype"
+ select="$clause/guard/subtype[typename = $variable]"/>
+
+ <xsl:if test="count($subtype) = 0">
+ <xsl:call-template name="err">
+ <xsl:with-param name="f" select="ancestor::erlref/module"/>
+ <xsl:with-param name="n" select="$fname"/>
+ <xsl:with-param name="a" select="$arity"/>
+ <xsl:with-param name="s">unknown type variable <xsl:value-of select="$variable"/>
+ </xsl:with-param>
+ </xsl:call-template>
+ </xsl:if>
+
+ <xsl:call-template name="subtype">
+ <xsl:with-param name="subtype" select="$subtype"/>
+ <xsl:with-param name="type_desc" select="$type_desc"/>
+ </xsl:call-template>
+ </xsl:for-each>
</xsl:template>
<xsl:template name="subtype">
<xsl:param name="subtype"/>
<xsl:param name="type_desc"/>
+
<xsl:for-each select="$subtype">
<xsl:variable name="tname" select="typename"/>
- <xsl:variable name="tdesc" select="$type_desc[@name = $tname]"/>
- <xsl:text>&#10;</xsl:text>
- <xsl:apply-templates select="string"/>
- <xsl:text>&#10;.br</xsl:text>
- <xsl:apply-templates select="$type_desc[@name = $tname]"/>
+ <xsl:text>&#10;</xsl:text>
+ <xsl:apply-templates select="string"/>
+ <xsl:text>&#10;.br</xsl:text>
+ <xsl:apply-templates select="$type_desc[@variable = $tname]"/>
+ </xsl:for-each>
+ </xsl:template>
+
+ <xsl:template name="local_type">
+ <xsl:param name="type_desc"/>
+ <xsl:param name="local_types"/>
+
+ <xsl:for-each select ="$local_types">
+ <xsl:text>&#10;</xsl:text>
+ <xsl:call-template name="type_name">
+ <xsl:with-param name="mode" select="'local_type'"/>
+ </xsl:call-template>
+ <xsl:text>&#10;.br</xsl:text>
+ <xsl:variable name="tname" select="@name"/>
+ <xsl:variable name="tnvars" select="@n_vars"/>
+ <xsl:apply-templates select=
+ "$type_desc[@name = $tname
+ and (@n_vars = $tnvars
+ or string-length(@n_vars) = 0 and
+ string-length($tnvars) = 0)]"/>
</xsl:for-each>
</xsl:template>
@@ -164,59 +269,46 @@
<xsl:apply-templates/>
</xsl:template>
- <xsl:template match="typehead">
- <xsl:text>&#10;.nf&#10;</xsl:text>
- <xsl:text>&#10;.B&#10;</xsl:text>
- <xsl:apply-templates/>
- <xsl:text>&#10;.br</xsl:text>
- <xsl:text>&#10;.fi</xsl:text>
- </xsl:template>
-
- <xsl:template match="local_defs">
- <xsl:text>&#10;.RS</xsl:text>
- <xsl:apply-templates/>
- <xsl:text>&#10;.RE</xsl:text>
- </xsl:template>
-
- <xsl:template match="local_def">
- <xsl:text>&#10;</xsl:text>
- <xsl:apply-templates/>
- <xsl:text>&#10;.br</xsl:text>
- </xsl:template>
-
<xsl:template name="type_name">
+ <xsl:param name="mode"/> <!-- '' if <datatype> -->
<xsl:variable name="curModule" select="ancestor::erlref/module"/>
<xsl:variable name="mod" select="@mod"/>
<xsl:variable name="name" select="@name"/>
- <xsl:variable name="n_vars">
- <xsl:choose>
- <xsl:when test="string-length(@n_vars) > 0">
- <xsl:value-of select="@n_vars"/>
- </xsl:when>
- <xsl:otherwise>
- <xsl:value-of select="0"/>
- </xsl:otherwise>
- </xsl:choose>
- </xsl:variable>
+ <xsl:variable name="n_vars" select="@n_vars"/>
<xsl:choose>
<xsl:when test="string-length($name) > 0">
<xsl:variable name="type" select=
"$i/module[@name=$curModule]/type
- [name=$name and n_vars=$n_vars
+ [name=$name
+ and (string-length($n_vars) = 0 or n_vars = $n_vars)
and (string-length($mod) = 0 or module = $mod)]"/>
<xsl:if test="count($type) != 1">
+ <xsl:variable name="why">
+ <xsl:choose>
+ <xsl:when test="count($type) > 1">ambiguous type</xsl:when>
+ <xsl:when test="count($type) = 0">unknown type</xsl:when>
+ </xsl:choose>
+ </xsl:variable>
<xsl:call-template name="err">
+ <xsl:with-param name="f" select="$curModule"/>
<xsl:with-param name="m" select="$mod"/>
<xsl:with-param name="n" select="$name"/>
<xsl:with-param name="a" select="$n_vars"/>
- <xsl:with-param name="s">unknown type</xsl:with-param>
+ <xsl:with-param name="s" select="$why"/>
</xsl:call-template>
</xsl:if>
- <xsl:apply-templates select="$type/typedecl"/>
+ <xsl:choose>
+ <xsl:when test="$mode = ''">
+ <xsl:apply-templates select="$type/typedecl"/>
+ </xsl:when>
+ <xsl:when test="$mode = 'local_type'">
+ <xsl:apply-templates select="$type/typedecl" mode="local_type"/>
+ </xsl:when>
+ </xsl:choose>
</xsl:when>
- <xsl:otherwise>
+ <xsl:otherwise> <!-- <datatype> with <name> -->
<xsl:text>&#10;.nf&#10;</xsl:text>
<xsl:text>&#10;.B&#10;</xsl:text>
<xsl:apply-templates/>
@@ -226,68 +318,109 @@
</xsl:choose>
</xsl:template>
+ <xsl:template match="typehead">
+ <xsl:text>&#10;.nf&#10;</xsl:text>
+ <xsl:text>&#10;.B&#10;</xsl:text>
+ <xsl:apply-templates/>
+ <xsl:text>&#10;.br</xsl:text>
+ <xsl:text>&#10;.fi</xsl:text>
+ </xsl:template>
+
+ <xsl:template match="typehead" mode="local_type">
+ <xsl:text>.nf&#10;</xsl:text>
+ <xsl:apply-templates/>
+ <xsl:text>&#10;.fi</xsl:text>
+ </xsl:template>
+
+ <!-- Not used right now -->
+ <xsl:template match="local_defs">
+ <xsl:text>&#10;.RS</xsl:text>
+ <xsl:apply-templates/>
+ <xsl:text>&#10;.RE</xsl:text>
+ </xsl:template>
+
+ <xsl:template match="local_def">
+ <xsl:text>&#10;</xsl:text>
+ <xsl:apply-templates/>
+ <xsl:text>&#10;.br</xsl:text>
+ </xsl:template>
+
<!-- Used both in <datatype> and in <func>! -->
<xsl:template match="anno">
<xsl:variable name="curModule" select="ancestor::erlref/module"/>
<xsl:variable name="anno" select="normalize-space(text())"/>
<xsl:variable name="namespec"
- select="ancestor::desc/preceding-sibling::name"/>
+ select="ancestor::type_desc/preceding-sibling::name
+ | ancestor::desc/preceding-sibling::name"/>
<xsl:if test="count($namespec) = 0 and string-length($specs_file) > 0">
<xsl:call-template name="err">
- <xsl:with-param name="s">cannot find 'name' (<xsl:value-of select="$anno"/>)
+ <xsl:with-param name="f" select="$curModule"/>
+ <xsl:with-param name="s">cannot find tag 'name' (anno <xsl:value-of select="$anno"/>)
</xsl:with-param>
</xsl:call-template>
</xsl:if>
- <xsl:variable name="mod" select="$namespec/@mod"/>
- <xsl:variable name="name" select="$namespec/@name"/>
- <xsl:variable name="arity" select="$namespec/@arity"/>
- <xsl:variable name="clause" select="$namespec/@clause"/>
- <xsl:variable name="tmp_n_vars" select="$namespec/@n_vars"/>
- <xsl:variable name="n_vars">
- <xsl:choose>
- <xsl:when test="string-length($tmp_n_vars) > 0">
- <xsl:value-of select="$tmp_n_vars"/>
- </xsl:when>
- <xsl:otherwise>
- <xsl:value-of select="0"/>
- </xsl:otherwise>
- </xsl:choose>
+ <!-- Search "local types" as well -->
+ <xsl:variable name="local_types"
+ select="ancestor::desc/preceding-sibling::type
+ [string-length(@name) > 0]"/>
+ <xsl:variable name="has_anno_in_local_type">
+ <xsl:for-each select="$local_types">
+ <xsl:call-template name="anno_name">
+ <xsl:with-param name="curModule" select="$curModule"/>
+ <xsl:with-param name="anno" select="$anno"/>
+ </xsl:call-template>
+ </xsl:for-each>
+ </xsl:variable>
+
+ <xsl:variable name="has_anno">
+ <xsl:for-each select="$namespec">
+ <xsl:call-template name="anno_name">
+ <xsl:with-param name="curModule" select="$curModule"/>
+ <xsl:with-param name="anno" select="$anno"/>
+ </xsl:call-template>
+ </xsl:for-each>
</xsl:variable>
+
+ <xsl:if test="$has_anno = '' and $has_anno_in_local_type = ''">
+ <xsl:call-template name="err">
+ <xsl:with-param name="f" select="$curModule"/>
+ <xsl:with-param name="m" select="$namespec/@mod"/>
+ <xsl:with-param name="n" select="$namespec/@name"/>
+ <xsl:with-param name="a" select="'-'"/>
+ <xsl:with-param name="s">unknown annotation <xsl:value-of select="$anno"/>
+ </xsl:with-param>
+ </xsl:call-template>
+ </xsl:if>
+ <xsl:value-of select="$anno"/>
+ </xsl:template>
+
+ <xsl:template name="anno_name">
+ <xsl:param name="curModule"/>
+ <xsl:param name="anno"/>
+ <xsl:variable name="mod" select="@mod"/>
+ <xsl:variable name="name" select="@name"/>
+ <xsl:variable name="arity" select="@arity"/>
+ <xsl:variable name="n_vars" select="@n_vars"/>
+ <xsl:variable name="clause_i" select="@clause_i"/>
+
<xsl:variable name="spec0" select=
"$i/module[@name=$curModule]/spec
[name=$name and arity=$arity
and (string-length($mod) = 0 or module = $mod)]"/>
<xsl:variable name="spec_annos" select=
- "$spec0[string-length($clause) = 0
- or position() = $clause]/anno[.=$anno]"/>
+ "$spec0[string-length($clause_i) = 0
+ or position() = $clause_i]/anno[.=$anno]"/>
<xsl:variable name="type_annos" select=
"$i/module[@name=$curModule]/type
- [name=$name and n_vars=$n_vars
+ [name=$name
+ and (string-length($n_vars) = 0 or n_vars=$n_vars)
and (string-length($mod) = 0 or module = $mod)]/anno[.=$anno]"/>
-
- <xsl:if test="count($spec_annos) = 0
- and count($type_annos) = 0
- and string-length($specs_file) > 0">
- <xsl:variable name="n">
- <xsl:choose>
- <xsl:when test="string-length($arity) = 0">
- <xsl:value-of select="$n_vars"/>
- </xsl:when>
- <xsl:otherwise>
- <xsl:value-of select="$arity"/>
- </xsl:otherwise>
- </xsl:choose>
- </xsl:variable>
- <xsl:call-template name="err">
- <xsl:with-param name="m" select="$mod"/>
- <xsl:with-param name="n" select="$name"/>
- <xsl:with-param name="a" select="$n"/>
- <xsl:with-param name="s">unknown annotation <xsl:value-of select="$anno"/>
- </xsl:with-param>
- </xsl:call-template>
+ <xsl:if test="count($spec_annos) != 0
+ or count($type_annos) != 0
+ or string-length($specs_file) = 0">
+ <xsl:value-of select="true()"/>
</xsl:if>
- <xsl:value-of select="$anno"/>
</xsl:template>
<!-- Used for indentation of formatted types and specs -->
@@ -542,6 +675,9 @@
<xsl:template match="func">
<xsl:text>&#10;.LP</xsl:text>
<xsl:apply-templates select="name"/>
+ <xsl:apply-templates
+ select="name[string-length(@arity) > 0 and position()=last()]"
+ mode="types"/>
<xsl:apply-templates select="fsummary|type|desc"/>
</xsl:template>
@@ -554,6 +690,11 @@
<xsl:when test="ancestor::datatype">
<xsl:call-template name="type_name"/>
</xsl:when>
+ <xsl:when test="string-length(text()) = 0 and ancestor::erlref">
+ <xsl:message terminate="yes">
+ Error <xsl:value-of select="@name"/>: arity is mandatory when referring to specifications!
+ </xsl:message>
+ </xsl:when>
<xsl:otherwise>
<xsl:call-template name="name"/>
</xsl:otherwise>
@@ -569,11 +710,14 @@
<!-- Type -->
<xsl:template match="type">
- <xsl:text>&#10;.RS</xsl:text>
- <xsl:text>&#10;.TP</xsl:text>
- <xsl:text>&#10;Types</xsl:text>
- <xsl:apply-templates/>
- <xsl:text>&#10;.RE</xsl:text>
+ <!-- The case where @name != 0 is taken care of in "type_name" -->
+ <xsl:if test="string-length(@name) = 0 and string-length(@variable) = 0">
+ <xsl:text>&#10;.RS</xsl:text>
+ <xsl:text>&#10;.TP</xsl:text>
+ <xsl:text>&#10;Types</xsl:text>
+ <xsl:apply-templates/>
+ <xsl:text>&#10;.RE</xsl:text>
+ </xsl:if>
</xsl:template>
@@ -622,7 +766,7 @@
<xsl:text>&gt;</xsl:text>
</xsl:template>
- <!-- Do not noramlize any text within pre and code tags. -->
+ <!-- Do not normalize any text within pre and code tags. -->
<xsl:template match="pre/text()">
<xsl:call-template name="replace-string">
<xsl:with-param name="text" select="." />
diff --git a/lib/erl_docgen/priv/xsl/db_pdf.xsl b/lib/erl_docgen/priv/xsl/db_pdf.xsl
index f500cd3fee..5119e3e36a 100644
--- a/lib/erl_docgen/priv/xsl/db_pdf.xsl
+++ b/lib/erl_docgen/priv/xsl/db_pdf.xsl
@@ -21,6 +21,8 @@
-->
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+ xmlns:exsl="http://exslt.org/common"
+ extension-element-prefixes="exsl"
xmlns:fo="http://www.w3.org/1999/XSL/Format">
<xsl:output method="xml" indent="yes"/>
@@ -28,45 +30,66 @@
<xsl:include href="db_pdf_params.xsl"/>
<!-- Start of Dialyzer type/spec tags.
- See also the template matching "name" and the template "bookmarks6"
+ See also the templates matching "name" and "seealso" as well as
+ the template "bookmarks6"
-->
<xsl:param name="specs_file" select="''"/>
<xsl:variable name="i" select="document($specs_file)"></xsl:variable>
<xsl:template name="err">
+ <xsl:param name="f"/>
<xsl:param name="m"/>
<xsl:param name="n"/>
<xsl:param name="a"/>
<xsl:param name="s"/>
<xsl:message terminate="yes">
- Error <xsl:if test="$m != ''"><xsl:value-of select ="$m"/>:</xsl:if>
- <xsl:value-of
- select="$n"/>/<xsl:value-of
- select="$a"/>: <xsl:value-of select="$s"/>
+ Error <xsl:if test="$f != ''">in <xsl:value-of select ="$f"/>:</xsl:if>
+ <xsl:if test="$m != ''"><xsl:value-of select ="$m"/>:</xsl:if>
+ <xsl:value-of select="$n"/>
+ <xsl:if test="$a != ''">/<xsl:value-of
+ select ="$a"/></xsl:if>: <xsl:value-of select="$s"/>
</xsl:message>
</xsl:template>
- <xsl:template name="spec_name">
+ <xsl:template name="find_spec">
<xsl:variable name="curModule" select="ancestor::erlref/module"/>
<xsl:variable name="mod" select="@mod"/>
<xsl:variable name="name" select="@name"/>
<xsl:variable name="arity" select="@arity"/>
- <xsl:variable name="clause" select="@clause"/>
+ <xsl:variable name="clause_i" select="@clause_i"/>
<xsl:variable name="spec0" select=
"$i/specs/module[@name=$curModule]/spec
[name=$name and arity=$arity
and (string-length($mod) = 0 or module = $mod)]"/>
- <xsl:variable name="spec" select="$spec0[string-length($clause) = 0
- or position() = $clause]"/>
- <xsl:if test="count($spec) = 0">
+ <xsl:variable name="spec" select="$spec0[string-length($clause_i) = 0
+ or position() = $clause_i]"/>
+
+ <xsl:if test="count($spec) != 1">
+ <xsl:variable name="why">
+ <xsl:choose>
+ <xsl:when test="count($spec) > 1">ambiguous spec</xsl:when>
+ <xsl:when test="count($spec) = 0">unknown spec</xsl:when>
+ </xsl:choose>
+ </xsl:variable>
<xsl:call-template name="err">
+ <xsl:with-param name="f" select="$curModule"/>
<xsl:with-param name="m" select="$mod"/>
<xsl:with-param name="n" select="$name"/>
<xsl:with-param name="a" select="$arity"/>
- <xsl:with-param name="s">unknown spec</xsl:with-param>
+ <xsl:with-param name="s" select="$why"/>
</xsl:call-template>
</xsl:if>
+ <xsl:copy-of select="$spec"/>
+ </xsl:template>
+
+ <xsl:template name="spec_name">
+ <xsl:variable name="name" select="@name"/>
+ <xsl:variable name="arity" select="@arity"/>
+ <xsl:variable name="spec0">
+ <xsl:call-template name="find_spec"/>
+ </xsl:variable>
+ <xsl:variable name="spec" select="exsl:node-set($spec0)/spec"/>
<xsl:choose>
<xsl:when test="ancestor::cref">
@@ -76,78 +99,175 @@
</xsl:when>
<xsl:when test="ancestor::erlref">
<fo:block id="{generate-id()}">
- <xsl:choose>
- <xsl:when test="string(@with_guards) = 'no'">
- <xsl:apply-templates select="$spec/contract/clause/head"/>
- </xsl:when>
- <xsl:otherwise>
- <xsl:call-template name="contract">
- <xsl:with-param name="contract" select="$spec/contract"/>
- </xsl:call-template>
- </xsl:otherwise>
- </xsl:choose>
+ <xsl:apply-templates select="$spec/contract/clause/head"/>
</fo:block>
</xsl:when>
</xsl:choose>
</xsl:template>
- <xsl:template name="contract">
- <xsl:param name="contract"/>
- <xsl:call-template name="clause">
- <xsl:with-param name="clause" select="$contract/clause"/>
- </xsl:call-template>
+ <xsl:template match="head">
+ <fo:block xsl:use-attribute-sets="function-name">
+ <xsl:apply-templates/>
+ </fo:block>
</xsl:template>
- <xsl:template name="clause">
- <xsl:param name="clause"/>
+ <!-- The *last* <name name="..." arity=".."/> -->
+ <xsl:template match="name" mode="types">
+ <xsl:variable name="name" select="@name"/>
+ <xsl:variable name="arity" select="@arity"/>
+ <xsl:variable name="spec0">
+ <xsl:call-template name="find_spec"/>
+ </xsl:variable>
+ <xsl:variable name="spec" select="exsl:node-set($spec0)/spec"/>
+ <xsl:variable name="clause" select="$spec/contract/clause"/>
+
<xsl:variable name="type_desc" select="../type_desc"/>
- <xsl:for-each select="$clause">
- <xsl:apply-templates select="head"/>
- <xsl:if test="count(guard) > 0">
- <xsl:call-template name="guard">
- <xsl:with-param name="guard" select="guard"/>
+ <!-- $type is data types to be presented as guards ("local types") -->
+ <xsl:variable name="type"
+ select="../type[string-length(@name) > 0
+ or string-length(@variable) > 0]"/>
+ <xsl:variable name="type_variables"
+ select ="$type[string-length(@variable) > 0]"/>
+ <xsl:variable name="local_types"
+ select ="$type[string-length(@name) > 0]"/>
+ <xsl:variable name="output_subtypes" select="count($type_variables) = 0"/>
+
+ <!-- It is assumed there is no support for overloaded specs
+ (there is no spec with more than one clause) -->
+ <xsl:if test="count($clause/guard) > 0 or count($type) > 0">
+ <fo:block>
+ <xsl:text>Types:</xsl:text>
+ </fo:block>
+ <fo:list-block xsl:use-attribute-sets="type-listblock">
+ <xsl:choose>
+ <xsl:when test="$output_subtypes">
+ <xsl:call-template name="subtype">
+ <xsl:with-param name="subtype" select="$clause/guard/subtype"/>
+ <xsl:with-param name="type_desc" select="$type_desc"/>
+ <xsl:with-param name="local_types" select="$local_types"/>
+ </xsl:call-template>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:call-template name="type_variables">
+ <xsl:with-param name="type_variables" select="$type_variables"/>
+ <xsl:with-param name="type_desc" select="$type_desc"/>
+ <xsl:with-param name="local_types" select="$local_types"/>
+ <xsl:with-param name="fname" select="$name"/>
+ <xsl:with-param name="arity" select="$arity"/>
+ </xsl:call-template>
+
+ </xsl:otherwise>
+ </xsl:choose>
+
+ <xsl:call-template name="local_type">
<xsl:with-param name="type_desc" select="$type_desc"/>
+ <xsl:with-param name="local_types" select="$local_types"/>
</xsl:call-template>
- </xsl:if>
- </xsl:for-each>
+ </fo:list-block>
+ </xsl:if>
</xsl:template>
- <xsl:template match="head">
- <fo:block xsl:use-attribute-sets="function-name">
- <xsl:apply-templates/>
- </fo:block>
- </xsl:template>
+ <!-- Handle <type variable="..." name_i="..."/> -->
+ <xsl:template name="type_variables">
+ <xsl:param name="type_variables"/>
+ <xsl:param name="type_desc"/>
+ <xsl:param name="local_types"/>
+ <xsl:param name="fname"/>
+ <xsl:param name="arity"/>
+
+ <xsl:variable name="names" select="../name[string-length(@arity) > 0]"/>
+ <xsl:for-each select="$type_variables">
+ <xsl:variable name="name_i">
+ <xsl:choose>
+ <xsl:when test="string-length(@name_i) > 0">
+ <xsl:value-of select="@name_i"/>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:value-of select="count($names)"/>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:variable>
+ <xsl:variable name="spec0">
+ <xsl:for-each select="$names[position() = $name_i]">
+ <xsl:call-template name="find_spec"/>
+ </xsl:for-each>
+ </xsl:variable>
+ <xsl:variable name="spec" select="exsl:node-set($spec0)/spec"/>
+ <xsl:variable name="clause" select="$spec/contract/clause"/>
+ <xsl:variable name="variable" select="@variable"/>
+ <xsl:variable name="subtype"
+ select="$clause/guard/subtype[typename = $variable]"/>
+
+ <xsl:if test="count($subtype) = 0">
+ <xsl:call-template name="err">
+ <xsl:with-param name="f" select="ancestor::erlref/module"/>
+ <xsl:with-param name="n" select="$fname"/>
+ <xsl:with-param name="a" select="$arity"/>
+ <xsl:with-param name="s">unknown type variable <xsl:value-of select="$variable"/>
+ </xsl:with-param>
+ </xsl:call-template>
+ </xsl:if>
- <xsl:template name="guard">
- <fo:block>
- <xsl:text>Types:</xsl:text>
- </fo:block>
- <fo:list-block xsl:use-attribute-sets="type-listblock">
<xsl:call-template name="subtype">
- <xsl:with-param name="subtype" select="$guard/subtype"/>
- <xsl:with-param name="type_desc" select="$type_desc"/>
+ <xsl:with-param name="subtype" select="$subtype"/>
+ <xsl:with-param name="type_desc" select="$type_desc"/>
</xsl:call-template>
- </fo:list-block>
+ </xsl:for-each>
</xsl:template>
+ <!-- Substituted
+ <fo:block xsl:use-attribute-sets="function-name">
+ for
+ <fo:block font-weight="bold">
+ to get proper indentation (a monospace font)
+ -->
<xsl:template name="subtype">
<xsl:param name="subtype"/>
<xsl:param name="type_desc"/>
+
<xsl:for-each select="$subtype">
<xsl:variable name="tname" select="typename"/>
- <xsl:variable name="tdesc" select="$type_desc[@name = $tname]"/>
<fo:list-item xsl:use-attribute-sets="type-listitem">
<fo:list-item-label end-indent="label-end()">
<fo:block>
</fo:block>
</fo:list-item-label>
<fo:list-item-body start-indent="body-start()" format="justify">
- <fo:block font-weight="bold">
- <xsl:apply-templates select="string"/>
+ <fo:block xsl:use-attribute-sets="function-name">
+ <xsl:apply-templates select="string"/>
</fo:block>
</fo:list-item-body>
</fo:list-item>
- <xsl:apply-templates select="$type_desc[@name = $tname]"/>
+ <xsl:apply-templates select="$type_desc[@variable = $tname]"/>
+ </xsl:for-each>
+ </xsl:template>
+
+ <xsl:template name="local_type">
+ <xsl:param name="type_desc"/>
+ <xsl:param name="local_types"/>
+
+ <xsl:for-each select="$local_types">
+ <fo:list-item xsl:use-attribute-sets="type-listitem">
+ <fo:list-item-label end-indent="label-end()">
+ <fo:block>
+ </fo:block>
+ </fo:list-item-label>
+ <fo:list-item-body start-indent="body-start()" format="justify">
+ <!-- <fo:block font-weight="bold">
+ (use function-name in "typehead" instead) -->
+ <xsl:call-template name="type_name">
+ <xsl:with-param name="mode" select="'local_type'"/>
+ </xsl:call-template>
+ <!-- </fo:block> -->
+ </fo:list-item-body>
+ </fo:list-item>
+ <xsl:variable name="tname" select="@name"/>
+ <xsl:variable name="tnvars" select="@n_vars"/>
+ <xsl:apply-templates select=
+ "$type_desc[@name = $tname
+ and (@n_vars = $tnvars
+ or string-length(@n_vars) = 0 and
+ string-length($tnvars) = 0)]"/>
</xsl:for-each>
</xsl:template>
@@ -182,6 +302,53 @@
<xsl:apply-templates select="desc"/>
</xsl:template>
+ <xsl:template name="type_name">
+ <xsl:param name="mode"/> <!-- '' if <datatype> -->
+ <xsl:variable name="curModule" select="ancestor::erlref/module"/>
+ <xsl:variable name="mod" select="@mod"/>
+ <xsl:variable name="name" select="@name"/>
+ <xsl:variable name="n_vars" select="@n_vars"/>
+
+ <xsl:choose>
+ <xsl:when test="string-length($name) > 0">
+ <xsl:variable name="type" select=
+ "$i/specs/module[@name=$curModule]/type
+ [name=$name
+ and (string-length($n_vars) = 0 or n_vars = $n_vars)
+ and (string-length($mod) = 0 or module = $mod)]"/>
+
+ <xsl:if test="count($type) != 1">
+ <xsl:variable name="why">
+ <xsl:choose>
+ <xsl:when test="count($type) > 1">ambiguous type</xsl:when>
+ <xsl:when test="count($type) = 0">unknown type</xsl:when>
+ </xsl:choose>
+ </xsl:variable>
+ <xsl:call-template name="err">
+ <xsl:with-param name="f" select="$curModule"/>
+ <xsl:with-param name="m" select="$mod"/>
+ <xsl:with-param name="n" select="$name"/>
+ <xsl:with-param name="a" select="$n_vars"/>
+ <xsl:with-param name="s" select="$why"/>
+ </xsl:call-template>
+ </xsl:if>
+ <xsl:choose>
+ <xsl:when test="$mode = ''">
+ <xsl:apply-templates select="$type/typedecl"/>
+ </xsl:when>
+ <xsl:when test="$mode = 'local_type'">
+ <xsl:apply-templates select="$type/typedecl" mode="local_type"/>
+ </xsl:when>
+ </xsl:choose>
+ </xsl:when>
+ <xsl:otherwise>
+ <fo:inline font-weight="bold" xsl:use-attribute-sets="type-listitem">
+ <xsl:value-of select="."/>
+ </fo:inline>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:template>
+
<!-- Like <head>... -->
<xsl:template match="typehead">
<fo:block xsl:use-attribute-sets="function-name">
@@ -189,6 +356,20 @@
</fo:block>
</xsl:template>
+ <!-- Substituted
+ <fo:block xsl:use-attribute-sets="function-name">
+ for
+ <fo:block font-weight="bold">
+ to get proper indentation (a monospace font)
+ -->
+
+ <xsl:template match="typehead" mode="local_type">
+ <fo:block xsl:use-attribute-sets="function-name">
+ <xsl:apply-templates/>
+ </fo:block>
+ </xsl:template>
+
+ <!-- Not used right now -->
<!-- Like <guard>, except "Types:"... -->
<xsl:template match="local_defs">
<fo:list-block xsl:use-attribute-sets="type-listblock">
@@ -211,108 +392,82 @@
</fo:list-item>
</xsl:template>
- <xsl:template name="type_name">
- <xsl:variable name="curModule" select="ancestor::erlref/module"/>
- <xsl:variable name="mod" select="@mod"/>
- <xsl:variable name="name" select="@name"/>
- <xsl:variable name="n_vars">
- <xsl:choose>
- <xsl:when test="string-length(@n_vars) > 0">
- <xsl:value-of select="@n_vars"/>
- </xsl:when>
- <xsl:otherwise>
- <xsl:value-of select="0"/>
- </xsl:otherwise>
- </xsl:choose>
- </xsl:variable>
-
- <xsl:choose>
- <xsl:when test="string-length($name) > 0">
- <xsl:variable name="type" select=
- "$i/specs/module[@name=$curModule]/type
- [name=$name and n_vars=$n_vars
- and (string-length($mod) = 0 or module = $mod)]"/>
-
- <xsl:if test="count($type) != 1">
- <xsl:call-template name="err">
- <xsl:with-param name="m" select="$mod"/>
- <xsl:with-param name="n" select="$name"/>
- <xsl:with-param name="a" select="$n_vars"/>
- <xsl:with-param name="s">unknown type</xsl:with-param>
- </xsl:call-template>
- </xsl:if>
- <xsl:apply-templates select="$type/typedecl"/>
- </xsl:when>
- <xsl:otherwise>
- <fo:inline font-weight="bold" xsl:use-attribute-sets="type-listitem">
- <xsl:value-of select="."/>
- </fo:inline>
- </xsl:otherwise>
- </xsl:choose>
- </xsl:template>
-
<!-- Used both in <datatype> and in <func>! -->
<xsl:template match="anno">
<xsl:variable name="curModule" select="ancestor::erlref/module"/>
<xsl:variable name="anno" select="normalize-space(text())"/>
<xsl:variable name="namespec"
- select="ancestor::desc/preceding-sibling::name"/>
+ select="ancestor::type_desc/preceding-sibling::name
+ | ancestor::desc/preceding-sibling::name"/>
<xsl:if test="count($namespec) = 0 and string-length($specs_file) > 0">
<xsl:call-template name="err">
- <xsl:with-param name="s">cannot find 'name' (<xsl:value-of select="$anno"/>)
+ <xsl:with-param name="f" select="$curModule"/>
+ <xsl:with-param name="s">cannot find tag 'name' (anno <xsl:value-of select="$anno"/>)
</xsl:with-param>
</xsl:call-template>
</xsl:if>
- <xsl:variable name="mod" select="$namespec/@mod"/>
- <xsl:variable name="name" select="$namespec/@name"/>
- <xsl:variable name="arity" select="$namespec/@arity"/>
- <xsl:variable name="clause" select="$namespec/@clause"/>
- <xsl:variable name="tmp_n_vars" select="$namespec/@n_vars"/>
- <xsl:variable name="n_vars">
- <xsl:choose>
- <xsl:when test="string-length($tmp_n_vars) > 0">
- <xsl:value-of select="$tmp_n_vars"/>
- </xsl:when>
- <xsl:otherwise>
- <xsl:value-of select="0"/>
- </xsl:otherwise>
- </xsl:choose>
+ <!-- Search "local types" as well -->
+ <xsl:variable name="local_types"
+ select="ancestor::desc/preceding-sibling::type
+ [string-length(@name) > 0]"/>
+ <xsl:variable name="has_anno_in_local_type">
+ <xsl:for-each select="$local_types">
+ <xsl:call-template name="anno_name">
+ <xsl:with-param name="curModule" select="$curModule"/>
+ <xsl:with-param name="anno" select="$anno"/>
+ </xsl:call-template>
+ </xsl:for-each>
+ </xsl:variable>
+
+ <xsl:variable name="has_anno">
+ <xsl:for-each select="$namespec">
+ <xsl:call-template name="anno_name">
+ <xsl:with-param name="curModule" select="$curModule"/>
+ <xsl:with-param name="anno" select="$anno"/>
+ </xsl:call-template>
+ </xsl:for-each>
</xsl:variable>
+
+ <xsl:if test="$has_anno = '' and $has_anno_in_local_type = ''">
+ <xsl:call-template name="err">
+ <xsl:with-param name="f" select="$curModule"/>
+ <xsl:with-param name="m" select="$namespec/@mod"/>
+ <xsl:with-param name="n" select="$namespec/@name"/>
+ <xsl:with-param name="a" select="'-'"/>
+ <xsl:with-param name="s">unknown annotation <xsl:value-of select="$anno"/>
+ </xsl:with-param>
+ </xsl:call-template>
+ </xsl:if>
+ <xsl:value-of select="$anno"/>
+ </xsl:template>
+
+ <xsl:template name="anno_name">
+ <xsl:param name="curModule"/>
+ <xsl:param name="anno"/>
+ <xsl:variable name="mod" select="@mod"/>
+ <xsl:variable name="name" select="@name"/>
+ <xsl:variable name="arity" select="@arity"/>
+ <xsl:variable name="n_vars" select="@n_vars"/>
+ <xsl:variable name="clause_i" select="@clause_i"/>
+
<xsl:variable name="spec0" select=
"$i/specs/module[@name=$curModule]/spec
[name=$name and arity=$arity
and (string-length($mod) = 0 or module = $mod)]"/>
<xsl:variable name="spec_annos" select=
- "$spec0[string-length($clause) = 0
- or position() = $clause]/anno[.=$anno]"/>
+ "$spec0[string-length($clause_i) = 0
+ or position() = $clause_i]/anno[.=$anno]"/>
<xsl:variable name="type_annos" select=
"$i/specs/module[@name=$curModule]/type
- [name=$name and n_vars=$n_vars
+ [name=$name
+ and (string-length($n_vars) = 0 or n_vars=$n_vars)
and (string-length($mod) = 0 or module = $mod)]/anno[.=$anno]"/>
-
- <xsl:if test="count($spec_annos) = 0
- and count($type_annos) = 0
- and string-length($specs_file) > 0">
- <xsl:variable name="n">
- <xsl:choose>
- <xsl:when test="string-length($arity) = 0">
- <xsl:value-of select="$n_vars"/>
- </xsl:when>
- <xsl:otherwise>
- <xsl:value-of select="$arity"/>
- </xsl:otherwise>
- </xsl:choose>
- </xsl:variable>
- <xsl:call-template name="err">
- <xsl:with-param name="m" select="$mod"/>
- <xsl:with-param name="n" select="$name"/>
- <xsl:with-param name="a" select="$n"/>
- <xsl:with-param name="s">unknown annotation <xsl:value-of select="$anno"/>
- </xsl:with-param>
- </xsl:call-template>
+ <xsl:if test="count($spec_annos) != 0
+ or count($type_annos) != 0
+ or string-length($specs_file) = 0">
+ <xsl:value-of select="true()"/>
</xsl:if>
- <xsl:value-of select="$anno"/>
</xsl:template>
<!-- Used for indentation of formatted types and specs -->
@@ -1227,6 +1382,9 @@
<xsl:param name="partnum"/>
<xsl:apply-templates select="name"/>
+ <xsl:apply-templates
+ select="name[string-length(@arity) > 0 and position()=last()]"
+ mode="types"/>
<xsl:apply-templates select="fsummary|type|desc">
<xsl:with-param name="partnum" select="$partnum"/>
@@ -1245,6 +1403,11 @@
<xsl:when test="ancestor::datatype">
<xsl:call-template name="type_name"/>
</xsl:when>
+ <xsl:when test="string-length(text()) = 0 and ancestor::erlref">
+ <xsl:message terminate="yes">
+ Error <xsl:value-of select="@name"/>: arity is mandatory when referring to specifications!
+ </xsl:message>
+ </xsl:when>
<xsl:otherwise>
<fo:block xsl:use-attribute-sets="function-name">
<xsl:call-template name="name">
@@ -1276,15 +1439,20 @@
<xsl:template match="type">
<xsl:param name="partnum"/>
- <fo:block>
- <xsl:text>Types:</xsl:text>
- </fo:block>
+ <!-- The case where @name != 0 is taken care of in "type_name" -->
+ <xsl:if test="string-length(@name) = 0 and string-length(@variable) = 0">
- <fo:list-block xsl:use-attribute-sets="type-listblock">
- <xsl:apply-templates>
- <xsl:with-param name="partnum" select="$partnum"/>
- </xsl:apply-templates>
- </fo:list-block>
+ <fo:block>
+ <xsl:text>Types:</xsl:text>
+ </fo:block>
+
+ <fo:list-block xsl:use-attribute-sets="type-listblock">
+ <xsl:apply-templates>
+ <xsl:with-param name="partnum" select="$partnum"/>
+ </xsl:apply-templates>
+ </fo:list-block>
+
+ </xsl:if>
</xsl:template>
@@ -1431,6 +1599,7 @@
</xsl:template>
+ <!-- Does not look at @n_vars -->
<xsl:template match="seealso">
<fo:inline font-style="italic">
<xsl:apply-templates/>
diff --git a/lib/erl_docgen/src/otp_specs.erl b/lib/erl_docgen/src/otp_specs.erl
index 728ddb2e6e..edb437a942 100644
--- a/lib/erl_docgen/src/otp_specs.erl
+++ b/lib/erl_docgen/src/otp_specs.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1996-2010. All Rights Reserved.
+%% Copyright Ericsson AB 1996-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
@@ -380,6 +380,8 @@ t_type([E=#xmlElement{name = float}]) ->
t_float(E);
t_type([#xmlElement{name = nil}]) ->
t_nil();
+t_type([#xmlElement{name = paren, content = Es}]) ->
+ t_paren(Es);
t_type([#xmlElement{name = list, content = Es}]) ->
t_list(Es);
t_type([#xmlElement{name = nonempty_list, content = Es}]) ->
@@ -416,6 +418,9 @@ t_float(E) ->
t_nil() ->
["[]"].
+t_paren(Es) ->
+ ["("] ++ t_utype(get_elem(type, Es)) ++ [")"].
+
t_list(Es) ->
["["] ++ t_utype(get_elem(type, Es)) ++ ["]"].
@@ -532,6 +537,8 @@ ot_type([E=#xmlElement{name = float}]) ->
ot_float(E);
ot_type([#xmlElement{name = nil}]) ->
ot_nil();
+ot_type([#xmlElement{name = paren, content = Es}]) ->
+ ot_paren(Es);
ot_type([#xmlElement{name = list, content = Es}]) ->
ot_list(Es);
ot_type([#xmlElement{name = nonempty_list, content = Es}]) ->
@@ -582,6 +589,9 @@ ot_float(E) ->
ot_nil() ->
{nil,0}.
+ot_paren(Es) ->
+ {paren_type,0,[ot_utype(get_elem(type, Es))]}.
+
ot_list(Es) ->
{type,0,list,[ot_utype(get_elem(type, Es))]}.
@@ -697,5 +707,7 @@ annos_type([#xmlElement{name = union, content = Es}]) ->
lists:flatmap(fun annos_elem/1, Es);
annos_type([E=#xmlElement{name = typevar}]) ->
annos_elem(E);
+annos_type([#xmlElement{name = paren, content = Es}]) ->
+ annos(get_elem(type, Es));
annos_type(_) ->
[].
diff --git a/lib/erl_interface/src/epmd/epmd_port.c b/lib/erl_interface/src/epmd/epmd_port.c
index 698c75c217..6450f285cc 100644
--- a/lib/erl_interface/src/epmd/epmd_port.c
+++ b/lib/erl_interface/src/epmd/epmd_port.c
@@ -228,11 +228,8 @@ int ei_epmd_port (struct in_addr *addr, const char *alive, int *dist)
return ei_epmd_port_tmo (addr, alive, dist, 0);
}
-int ei_epmd_port_tmo (struct in_addr *addr, const char *alive, int *dist,
- unsigned ms)
+int ei_epmd_port_tmo (struct in_addr *addr, const char *alive, int *dist, unsigned ms)
{
- int i;
-
- return ei_epmd_r4_port(addr,alive,dist,ms);
+ return ei_epmd_r4_port(addr,alive,dist,ms);
}
diff --git a/lib/kernel/src/inet_res.erl b/lib/kernel/src/inet_res.erl
index d5a8a2f134..2276ddcd08 100644
--- a/lib/kernel/src/inet_res.erl
+++ b/lib/kernel/src/inet_res.erl
@@ -872,7 +872,7 @@ query_ns(S0, Id, Buffer, IP, Port, Timer, Retry, I,
end
end.
-query_udp(_S, _Id, _Buffer, _IP, _Port, 0, Verbose) ->
+query_udp(_S, _Id, _Buffer, _IP, _Port, 0, _Verbose) ->
timeout;
query_udp(S, Id, Buffer, IP, Port, Timeout, Verbose) ->
?verbose(Verbose, "Try UDP server : ~p:~p (timeout=~w)\n",
@@ -908,7 +908,7 @@ query_udp(S, Id, Buffer, IP, Port, Timeout, Verbose) ->
{error,econnrefused}
end.
-query_tcp(0, _Id, _Buffer, _IP, _Port, Verbose) ->
+query_tcp(0, _Id, _Buffer, _IP, _Port, _Verbose) ->
timeout;
query_tcp(Timeout, Id, Buffer, IP, Port, Verbose) ->
?verbose(Verbose, "Try TCP server : ~p:~p (timeout=~w)\n",
diff --git a/lib/mnesia/doc/src/Mnesia_chap7.xmlsrc b/lib/mnesia/doc/src/Mnesia_chap7.xmlsrc
index 7078499fbf..21174340d1 100644
--- a/lib/mnesia/doc/src/Mnesia_chap7.xmlsrc
+++ b/lib/mnesia/doc/src/Mnesia_chap7.xmlsrc
@@ -473,6 +473,13 @@ dets:close(N). </pre>
<c>mnesia:table_info(Tab, master_nodes)</c> may be used to
obtain information about the potential master nodes.
</p>
+ <p>Determining which data to keep after communication failure is outside
+ the scope of Mnesia. One approach would be to determine which "island"
+ contains a majority of the nodes. Using the <c>{majority,true}</c> option
+ for critical tables can be a way of ensuring that nodes that are not part
+ of a "majority island" are not able to update those tables. Note that this
+ constitutes a reduction in service on the minority nodes. This would be
+ a tradeoff in favour of higher consistency guarantees.</p>
<p>The function <c>mnesia:force_load_table(Tab)</c> may be used to
force load the table regardless of which table load mechanism
is activated.
diff --git a/lib/mnesia/doc/src/mnesia.xml b/lib/mnesia/doc/src/mnesia.xml
index 16e78ea0af..2a2c7d3a9f 100644
--- a/lib/mnesia/doc/src/mnesia.xml
+++ b/lib/mnesia/doc/src/mnesia.xml
@@ -161,6 +161,14 @@ If a new item is inserted with the same key as
</p>
</item>
<item>
+ <p><c>majority</c> This attribute can be either <c>true</c> or
+ <c>false</c> (default is <c>false</c>). When <c>true</c>, a majority
+ of the table replicas must be available for an update to succeed.
+ Majority checking can be enabled on tables with mission-critical data,
+ where it is vital to avoid inconsistencies due to network splits.
+ </p>
+ </item>
+ <item>
<p><c>snmp</c> Each (set based) Mnesia table can be
automatically turned into an SNMP ordered table as well.
This property specifies the types of the SNMP keys.
@@ -650,6 +658,17 @@ mnesia:change_table_copy_type(person, node(), disc_copies)
</desc>
</func>
<func>
+ <name>change_table_majority(Tab, Majority) -> {aborted, R} | {atomic, ok}</name>
+ <fsummary>Change the majority check setting for the table.</fsummary>
+ <desc>
+ <p><c>Majority</c> must be a boolean; the default is <c>false</c>.
+ When <c>true</c>, a majority of the table's replicas must be available
+ for an update to succeed. When used on fragmented tables, <c>Tab</c>
+ must be the name base table. Directly changing the majority setting on
+ individual fragments is not allowed.</p>
+ </desc>
+ </func>
+ <func>
<name>clear_table(Tab) -> {aborted, R} | {atomic, ok}</name>
<fsummary>Deletes all entries in a table.</fsummary>
<desc>
@@ -753,6 +772,14 @@ mnesia:change_table_copy_type(person, node(), disc_copies)
priority will be loaded first at startup.
</p>
</item>
+ <item>
+ <p><c>{majority, Flag}</c>, where <c>Flag</c> must be a boolean.
+ If <c>true</c>, any (non-dirty) update to the table will abort unless
+ a majority of the table's replicas are available for the commit.
+ When used on a fragmented table, all fragments will be given
+ the same majority setting.
+ </p>
+ </item>
<item>
<p><c>{ram_copies, Nodelist}</c>, where
<c>Nodelist</c> is a list of the nodes where this table
@@ -1737,7 +1764,10 @@ mnesia:create_table(person,
<c>write</c> and <c>sticky_write</c> are supported.
</p>
<p>If the user wants to update the record it is more efficient to
- use <c>write/sticky_write</c> as the LockKind.
+ use <c>write/sticky_write</c> as the LockKind. If majority checking
+ is active on the table, it will be checked as soon as a write lock is
+ attempted. This can be used to quickly abort if the majority condition
+ isn't met.
</p>
</desc>
</func>
diff --git a/lib/mnesia/src/mnesia.erl b/lib/mnesia/src/mnesia.erl
index 025b32f506..980a9c6213 100644
--- a/lib/mnesia/src/mnesia.erl
+++ b/lib/mnesia/src/mnesia.erl
@@ -39,6 +39,7 @@
%% Access within an activity - Lock acquisition
lock/2, lock/4,
+ lock_table/2,
read_lock_table/1,
write_lock_table/1,
@@ -92,7 +93,7 @@
add_table_copy/3, del_table_copy/2, move_table_copy/3,
add_table_index/2, del_table_index/2,
transform_table/3, transform_table/4,
- change_table_copy_type/3,
+ change_table_copy_type/3, change_table_majority/2,
read_table_property/2, write_table_property/2, delete_table_property/2,
change_table_frag/2,
clear_table/1, clear_table/4,
@@ -415,6 +416,9 @@ lock(LockItem, LockKind) ->
abort(no_transaction)
end.
+lock_table(Tab, LockKind) ->
+ lock({table, Tab}, LockKind).
+
lock(Tid, Ts, LockItem, LockKind) ->
case element(1, Tid) of
tid ->
@@ -467,6 +471,8 @@ lock_table(Tid, Ts, Tab, LockKind) when is_atom(Tab) ->
mnesia_locker:rlock_table(Tid, Store, Tab);
write ->
mnesia_locker:wlock_table(Tid, Store, Tab);
+ load ->
+ mnesia_locker:load_lock_table(Tid, Store, Tab);
sticky_write ->
mnesia_locker:sticky_wlock_table(Tid, Store, Tab);
none ->
@@ -2455,6 +2461,9 @@ change_table_access_mode(T, Access) ->
change_table_load_order(T, O) ->
mnesia_schema:change_table_load_order(T, O).
+change_table_majority(T, M) ->
+ mnesia_schema:change_table_majority(T, M).
+
set_master_nodes(Nodes) when is_list(Nodes) ->
UseDir = system_info(use_dir),
IsRunning = system_info(is_running),
diff --git a/lib/mnesia/src/mnesia.hrl b/lib/mnesia/src/mnesia.hrl
index d488d9364a..26537815a3 100644
--- a/lib/mnesia/src/mnesia.hrl
+++ b/lib/mnesia/src/mnesia.hrl
@@ -62,6 +62,7 @@
disc_only_copies = [], % [Node]
load_order = 0, % Integer
access_mode = read_write, % read_write | read_only
+ majority = false, % true | false
index = [], % [Integer]
snmp = [], % Snmp Ustruct
local_content = false, % true | false
diff --git a/lib/mnesia/src/mnesia_controller.erl b/lib/mnesia/src/mnesia_controller.erl
index 0254769758..d4b2c7b5cc 100644
--- a/lib/mnesia/src/mnesia_controller.erl
+++ b/lib/mnesia/src/mnesia_controller.erl
@@ -72,6 +72,7 @@
add_active_replica/4,
update/1,
change_table_access_mode/1,
+ change_table_majority/1,
del_active_replica/2,
wait_for_tables/2,
get_network_copy/2,
@@ -690,7 +691,8 @@ handle_call({update_where_to_write, [add, Tab, AddNode], _From}, _Dummy, State)
case lists:member(AddNode, Current) and
(State#state.schema_is_merged == true) of
true ->
- mnesia_lib:add_lsort({Tab, where_to_write}, AddNode);
+ mnesia_lib:add_lsort({Tab, where_to_write}, AddNode),
+ update_where_to_wlock(Tab);
false ->
ignore
end,
@@ -1690,6 +1692,8 @@ add_active_replica(Tab, Node, Storage, AccessMode) ->
set(Var, mark_blocked_tab(Blocked, Del)),
mnesia_lib:del({Tab, where_to_write}, Node)
end,
+
+ update_where_to_wlock(Tab),
add({Tab, active_replicas}, Node).
del_active_replica(Tab, Node) ->
@@ -1699,7 +1703,8 @@ del_active_replica(Tab, Node) ->
New = lists:sort(Del),
set(Var, mark_blocked_tab(Blocked, New)), % where_to_commit
mnesia_lib:del({Tab, active_replicas}, Node),
- mnesia_lib:del({Tab, where_to_write}, Node).
+ mnesia_lib:del({Tab, where_to_write}, Node),
+ update_where_to_wlock(Tab).
change_table_access_mode(Cs) ->
W = fun() ->
@@ -1708,7 +1713,22 @@ change_table_access_mode(Cs) ->
val({Tab, active_replicas}))
end,
update(W).
-
+
+change_table_majority(Cs) ->
+ W = fun() ->
+ Tab = Cs#cstruct.name,
+ set({Tab, majority}, Cs#cstruct.majority),
+ update_where_to_wlock(Tab)
+ end,
+ update(W).
+
+update_where_to_wlock(Tab) ->
+ WNodes = val({Tab, where_to_write}),
+ Majority = case catch val({Tab, majority}) of
+ true -> true;
+ _ -> false
+ end,
+ set({Tab, where_to_wlock}, {WNodes, Majority}).
%% node To now has tab loaded, but this must be undone
%% This code is rpc:call'ed from the tab_copier process
diff --git a/lib/mnesia/src/mnesia_dumper.erl b/lib/mnesia/src/mnesia_dumper.erl
index 55b9946ae9..92fd9dfade 100644
--- a/lib/mnesia/src/mnesia_dumper.erl
+++ b/lib/mnesia/src/mnesia_dumper.erl
@@ -900,6 +900,14 @@ insert_op(Tid, _, {op, change_table_access_mode,TabDef, _OldAccess, _Access}, In
end,
insert_cstruct(Tid, Cs, true, InPlace, InitBy);
+insert_op(Tid, _, {op, change_table_majority,TabDef, _OldAccess, _Access}, InPlace, InitBy) ->
+ Cs = mnesia_schema:list2cs(TabDef),
+ case InitBy of
+ startup -> ignore;
+ _ -> mnesia_controller:change_table_majority(Cs)
+ end,
+ insert_cstruct(Tid, Cs, true, InPlace, InitBy);
+
insert_op(Tid, _, {op, change_table_load_order, TabDef, _OldLevel, _Level}, InPlace, InitBy) ->
Cs = mnesia_schema:list2cs(TabDef),
insert_cstruct(Tid, Cs, true, InPlace, InitBy);
diff --git a/lib/mnesia/src/mnesia_lib.erl b/lib/mnesia/src/mnesia_lib.erl
index 36bcfe8de9..7e926a6258 100644
--- a/lib/mnesia/src/mnesia_lib.erl
+++ b/lib/mnesia/src/mnesia_lib.erl
@@ -96,6 +96,8 @@
exists/1,
fatal/2,
get_node_number/0,
+ have_majority/2,
+ have_majority/3,
fix_error/1,
important/2,
incr_counter/1,
@@ -660,6 +662,14 @@ proc_info(_) -> false.
get_node_number() ->
{node(), self()}.
+have_majority(Tab, HaveNodes) ->
+ have_majority(Tab, val({Tab, all_nodes}), HaveNodes).
+
+have_majority(_Tab, AllNodes, HaveNodes) ->
+ Missing = AllNodes -- HaveNodes,
+ Present = AllNodes -- Missing,
+ length(Present) > length(Missing).
+
read_log_files() ->
[{F, catch file:read_file(F)} || F <- mnesia_log:log_files()].
diff --git a/lib/mnesia/src/mnesia_loader.erl b/lib/mnesia/src/mnesia_loader.erl
index 3de329503e..e785b795d1 100644
--- a/lib/mnesia/src/mnesia_loader.erl
+++ b/lib/mnesia/src/mnesia_loader.erl
@@ -702,7 +702,7 @@ send_table(Pid, Tab, RemoteS) ->
prepare_copy(Pid, Tab, Storage) ->
Trans =
fun() ->
- mnesia:write_lock_table(Tab),
+ mnesia:lock_table(Tab, load),
mnesia_subscr:subscribe(Pid, {table, Tab}),
update_where_to_write(Tab, node(Pid)),
mnesia_lib:db_fixtable(Storage, Tab, true),
diff --git a/lib/mnesia/src/mnesia_locker.erl b/lib/mnesia/src/mnesia_locker.erl
index ca0cc79c45..0492d794f3 100644
--- a/lib/mnesia/src/mnesia_locker.erl
+++ b/lib/mnesia/src/mnesia_locker.erl
@@ -40,7 +40,8 @@
sticky_wlock_table/3,
wlock/3,
wlock_no_exist/4,
- wlock_table/3
+ wlock_table/3,
+ load_lock_table/3
]).
%% sys callback functions
@@ -656,16 +657,17 @@ rwlock(Tid, Store, Oid) ->
Lock = write,
case need_lock(Store, Tab, Key, Lock) of
yes ->
- Ns = w_nodes(Tab),
+ {Ns, Majority} = w_nodes(Tab),
+ check_majority(Majority, Tab, Ns),
Res = get_rwlocks_on_nodes(Ns, rwlock, Node, Store, Tid, Oid),
?ets_insert(Store, {{locks, Tab, Key}, Lock}),
Res;
no ->
if
Key == ?ALL ->
- w_nodes(Tab);
+ element(2, w_nodes(Tab));
Tab == ?GLOBAL ->
- w_nodes(Tab);
+ element(2, w_nodes(Tab));
true ->
dirty_rpc(Node, Tab, Key, Lock)
end
@@ -677,12 +679,34 @@ rwlock(Tid, Store, Oid) ->
%% in the local store under the key == nodes
w_nodes(Tab) ->
- Nodes = ?catch_val({Tab, where_to_write}),
- case Nodes of
- [_ | _] -> Nodes;
+ case ?catch_val({Tab, where_to_wlock}) of
+ {[_ | _], _} = Where -> Where;
_ -> mnesia:abort({no_exists, Tab})
end.
+%% If the table has the 'majority' flag set, we can
+%% only take a write lock if we see a majority of the
+%% nodes.
+
+
+check_majority(true, Tab, HaveNs) ->
+ check_majority(Tab, HaveNs);
+check_majority(false, _, _) ->
+ ok.
+
+check_majority(Tab, HaveNs) ->
+ case ?catch_val({Tab, majority}) of
+ true ->
+ case mnesia_lib:have_majority(Tab, HaveNs) of
+ true ->
+ ok;
+ false ->
+ mnesia:abort({no_majority, Tab})
+ end;
+ _ ->
+ ok
+ end.
+
%% aquire a sticky wlock, a sticky lock is a lock
%% which remains at this node after the termination of the
%% transaction.
@@ -708,12 +732,14 @@ sticky_lock(Tid, Store, {Tab, Key} = Oid, Lock) ->
end.
do_sticky_lock(Tid, Store, {Tab, Key} = Oid, Lock) ->
+ {WNodes, Majority} = w_nodes(Tab),
+ sticky_check_majority(Lock, Tab, Majority, WNodes),
?MODULE ! {self(), {test_set_sticky, Tid, Oid, Lock}},
N = node(),
receive
{?MODULE, N, granted} ->
?ets_insert(Store, {{locks, Tab, Key}, write}),
- [?ets_insert(Store, {nodes, Node}) || Node <- w_nodes(Tab)],
+ [?ets_insert(Store, {nodes, Node}) || Node <- WNodes],
granted;
{?MODULE, N, {granted, Val}} -> %% for rwlocks
case opt_lookup_in_client(Val, Oid, write) of
@@ -721,7 +747,7 @@ do_sticky_lock(Tid, Store, {Tab, Key} = Oid, Lock) ->
exit({aborted, C});
Val2 ->
?ets_insert(Store, {{locks, Tab, Key}, write}),
- [?ets_insert(Store, {nodes, Node}) || Node <- w_nodes(Tab)],
+ [?ets_insert(Store, {nodes, Node}) || Node <- WNodes],
Val2
end;
{?MODULE, N, {not_granted, Reason}} ->
@@ -737,6 +763,16 @@ do_sticky_lock(Tid, Store, {Tab, Key} = Oid, Lock) ->
dirty_sticky_lock(Tab, Key, [N], Lock)
end.
+sticky_check_majority(W, Tab, true, WNodes) when W==write; W==read_write ->
+ case mnesia_lib:have_majority(Tab, WNodes) of
+ true ->
+ ok;
+ false ->
+ mnesia:abort({no_majority, Tab})
+ end;
+sticky_check_majority(_, _, _, _) ->
+ ok.
+
not_stuck(Tid, Store, Tab, _Key, Oid, _Lock, N) ->
rlock(Tid, Store, {Tab, ?ALL}), %% needed?
wlock(Tid, Store, Oid), %% perfect sync
@@ -773,22 +809,33 @@ sticky_wlock_table(Tid, Store, Tab) ->
%% local store when we have aquired the lock.
%%
wlock(Tid, Store, Oid) ->
+ wlock(Tid, Store, Oid, _CheckMajority = true).
+
+wlock(Tid, Store, Oid, CheckMajority) ->
{Tab, Key} = Oid,
case need_lock(Store, Tab, Key, write) of
yes ->
- Ns = w_nodes(Tab),
+ {Ns, Majority} = w_nodes(Tab),
+ if CheckMajority ->
+ check_majority(Majority, Tab, Ns);
+ true ->
+ ignore
+ end,
Op = {self(), {write, Tid, Oid}},
?ets_insert(Store, {{locks, Tab, Key}, write}),
get_wlocks_on_nodes(Ns, Ns, Store, Op, Oid);
no when Key /= ?ALL, Tab /= ?GLOBAL ->
[];
no ->
- w_nodes(Tab)
+ element(2, w_nodes(Tab))
end.
wlock_table(Tid, Store, Tab) ->
wlock(Tid, Store, {Tab, ?ALL}).
+load_lock_table(Tid, Store, Tab) ->
+ wlock(Tid, Store, {Tab, ?ALL}, _CheckMajority = false).
+
%% Write lock even if the table does not exist
wlock_no_exist(Tid, Store, Tab, Ns) ->
diff --git a/lib/mnesia/src/mnesia_schema.erl b/lib/mnesia/src/mnesia_schema.erl
index f33a6c7a84..fef72ad39c 100644
--- a/lib/mnesia/src/mnesia_schema.erl
+++ b/lib/mnesia/src/mnesia_schema.erl
@@ -37,6 +37,7 @@
change_table_copy_type/3,
change_table_access_mode/2,
change_table_load_order/2,
+ change_table_majority/2,
change_table_frag/2,
clear_table/1,
create_table/1,
@@ -178,6 +179,8 @@ do_set_schema(Tab, Cs) ->
set({Tab, disc_only_copies}, Cs#cstruct.disc_only_copies),
set({Tab, load_order}, Cs#cstruct.load_order),
set({Tab, access_mode}, Cs#cstruct.access_mode),
+ set({Tab, majority}, Cs#cstruct.majority),
+ set({Tab, all_nodes}, mnesia_lib:cs_to_nodes(Cs)),
set({Tab, snmp}, Cs#cstruct.snmp),
set({Tab, user_properties}, Cs#cstruct.user_properties),
[set({Tab, user_property, element(1, P)}, P) || P <- Cs#cstruct.user_properties],
@@ -651,6 +654,7 @@ list2cs(List) when is_list(List) ->
Snmp = pick(Name, snmp, List, []),
LoadOrder = pick(Name, load_order, List, 0),
AccessMode = pick(Name, access_mode, List, read_write),
+ Majority = pick(Name, majority, List, false),
UserProps = pick(Name, user_properties, List, []),
verify({alt, [nil, list]}, mnesia_lib:etype(UserProps),
{bad_type, Name, {user_properties, UserProps}}),
@@ -676,6 +680,7 @@ list2cs(List) when is_list(List) ->
snmp = Snmp,
load_order = LoadOrder,
access_mode = AccessMode,
+ majority = Majority,
local_content = LC,
record_name = RecName,
attributes = Attrs,
@@ -809,7 +814,16 @@ verify_cstruct(Cs) when is_record(Cs, cstruct) ->
Access = Cs#cstruct.access_mode,
verify({alt, [read_write, read_only]}, Access,
{bad_type, Tab, {access_mode, Access}}),
-
+ Majority = Cs#cstruct.majority,
+ verify({alt, [true, false]}, Majority,
+ {bad_type, Tab, {majority, Majority}}),
+ case Majority of
+ true ->
+ verify(false, LC,
+ {combine_error, Tab, [{local_content,true},{majority,true}]});
+ false ->
+ ok
+ end,
Snmp = Cs#cstruct.snmp,
verify(true, mnesia_snmp_hook:check_ustruct(Snmp),
{badarg, Tab, {snmp, Snmp}}),
@@ -1495,6 +1509,43 @@ make_change_table_load_order(Tab, LoadOrder) ->
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+change_table_majority(Tab, Majority) when is_boolean(Majority) ->
+ schema_transaction(fun() -> do_change_table_majority(Tab, Majority) end).
+
+do_change_table_majority(schema, _Majority) ->
+ mnesia:abort({bad_type, schema});
+do_change_table_majority(Tab, Majority) ->
+ TidTs = get_tid_ts_and_lock(schema, write),
+ get_tid_ts_and_lock(Tab, none),
+ insert_schema_ops(TidTs, make_change_table_majority(Tab, Majority)).
+
+make_change_table_majority(Tab, Majority) ->
+ ensure_writable(schema),
+ Cs = incr_version(val({Tab, cstruct})),
+ ensure_active(Cs),
+ OldMajority = Cs#cstruct.majority,
+ Cs2 = Cs#cstruct{majority = Majority},
+ FragOps = case lists:keyfind(base_table, 1, Cs#cstruct.frag_properties) of
+ {_, Tab} ->
+ FragNames = mnesia_frag:frag_names(Tab) -- [Tab],
+ lists:map(
+ fun(T) ->
+ get_tid_ts_and_lock(Tab, none),
+ CsT = incr_version(val({T, cstruct})),
+ ensure_active(CsT),
+ CsT2 = CsT#cstruct{majority = Majority},
+ verify_cstruct(CsT2),
+ {op, change_table_majority, cs2list(CsT2),
+ OldMajority, Majority}
+ end, FragNames);
+ false -> [];
+ {_, _} -> mnesia:abort({bad_type, Tab})
+ end,
+ verify_cstruct(Cs2),
+ [{op, change_table_majority, cs2list(Cs2), OldMajority, Majority} | FragOps].
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
write_table_property(Tab, Prop) when is_tuple(Prop), size(Prop) >= 1 ->
schema_transaction(fun() -> do_write_table_property(Tab, Prop) end);
write_table_property(Tab, Prop) ->
@@ -2974,6 +3025,7 @@ merge_versions(AnythingNew, Cs, RemoteCs, Force) ->
Cs#cstruct.index == RemoteCs#cstruct.index,
Cs#cstruct.snmp == RemoteCs#cstruct.snmp,
Cs#cstruct.access_mode == RemoteCs#cstruct.access_mode,
+ Cs#cstruct.majority == RemoteCs#cstruct.majority,
Cs#cstruct.load_order == RemoteCs#cstruct.load_order,
Cs#cstruct.user_properties == RemoteCs#cstruct.user_properties ->
do_merge_versions(AnythingNew, Cs, RemoteCs);
diff --git a/lib/mnesia/src/mnesia_tm.erl b/lib/mnesia/src/mnesia_tm.erl
index bb8e788b40..f62f7cb7c8 100644
--- a/lib/mnesia/src/mnesia_tm.erl
+++ b/lib/mnesia/src/mnesia_tm.erl
@@ -64,7 +64,8 @@
prev_tab = [], % initiate to a non valid table name
prev_types,
prev_snmp,
- types
+ types,
+ majority = []
}).
-record(participant, {tid, pid, commit, disc_nodes = [],
@@ -1100,9 +1101,12 @@ t_commit(Type) ->
case arrange(Tid, Store, Type) of
{N, Prep} when N > 0 ->
multi_commit(Prep#prep.protocol,
+ majority_attr(Prep),
Tid, Prep#prep.records, Store);
{0, Prep} ->
- multi_commit(read_only, Tid, Prep#prep.records, Store)
+ multi_commit(read_only,
+ majority_attr(Prep),
+ Tid, Prep#prep.records, Store)
end;
true ->
%% nested commit
@@ -1117,6 +1121,10 @@ t_commit(Type) ->
do_commit_nested
end.
+majority_attr(#prep{majority = M}) ->
+ M.
+
+
%% This function arranges for all objects we shall write in S to be
%% in a list of {Node, CommitRecord}
%% Important function for the performance of mnesia.
@@ -1222,11 +1230,13 @@ prepare_items(Tid, Tab, Key, Items, Prep) ->
{blocked, _} ->
unblocked = req({unblock_me, Tab}),
prepare_items(Tid, Tab, Key, Items, Prep);
- _ ->
+ _ ->
+ Majority = needs_majority(Tab, Prep),
Snmp = val({Tab, snmp}),
Recs2 = do_prepare_items(Tid, Tab, Key, Types,
Snmp, Items, Prep#prep.records),
Prep2 = Prep#prep{records = Recs2, prev_tab = Tab,
+ majority = Majority,
prev_types = Types, prev_snmp = Snmp},
check_prep(Prep2, Types)
end.
@@ -1235,6 +1245,33 @@ do_prepare_items(Tid, Tab, Key, Types, Snmp, Items, Recs) ->
Recs2 = prepare_snmp(Tid, Tab, Key, Types, Snmp, Items, Recs), % May exit
prepare_nodes(Tid, Types, Items, Recs2, normal).
+
+needs_majority(Tab, #prep{majority = M}) ->
+ case lists:keymember(Tab, 1, M) of
+ true ->
+ M;
+ false ->
+ case ?catch_val({Tab, majority}) of
+ {'EXIT', _} ->
+ M;
+ false ->
+ M;
+ true ->
+ CopyHolders = val({Tab, all_nodes}),
+ [{Tab, CopyHolders} | M]
+ end
+ end.
+
+have_majority([], _) ->
+ ok;
+have_majority([{Tab, AllNodes} | Rest], Nodes) ->
+ case mnesia_lib:have_majority(Tab, AllNodes, Nodes) of
+ true ->
+ have_majority(Rest, Nodes);
+ false ->
+ {error, Tab}
+ end.
+
prepare_snmp(Tab, Key, Items) ->
case val({Tab, snmp}) of
[] ->
@@ -1261,10 +1298,15 @@ prepare_snmp(Tid, Tab, Key, Types, Us, Items, Recs) ->
prepare_nodes(Tid, Types, [{clear_table, Tab}], Recs, snmp)
end.
-check_prep(Prep, Types) when Prep#prep.types == Types ->
+check_prep(#prep{majority = [], types = Types} = Prep, Types) ->
Prep;
-check_prep(Prep, Types) when Prep#prep.types == undefined ->
- Prep#prep{types = Types};
+check_prep(#prep{majority = M, types = undefined} = Prep, Types) ->
+ Protocol = if M == [] ->
+ Prep#prep.protocol;
+ true ->
+ asym_trans
+ end,
+ Prep#prep{protocol = Protocol, types = Types};
check_prep(Prep, _Types) ->
Prep#prep{protocol = asym_trans}.
@@ -1311,7 +1353,7 @@ prepare_node(_Node, _Storage, [], Rec, _Kind) ->
%% multi_commit((Protocol, Tid, CommitRecords, Store)
%% Local work is always performed in users process
-multi_commit(read_only, Tid, CR, _Store) ->
+multi_commit(read_only, _Maj = [], Tid, CR, _Store) ->
%% This featherweight commit protocol is used when no
%% updates has been performed in the transaction.
@@ -1324,7 +1366,7 @@ multi_commit(read_only, Tid, CR, _Store) ->
?MODULE ! {delete_transaction, Tid},
do_commit;
-multi_commit(sym_trans, Tid, CR, Store) ->
+multi_commit(sym_trans, _Maj = [], Tid, CR, Store) ->
%% This lightweight commit protocol is used when all
%% the involved tables are replicated symetrically.
%% Their storage types must match on each node.
@@ -1376,7 +1418,7 @@ multi_commit(sym_trans, Tid, CR, Store) ->
[{tid, Tid}, {outcome, Outcome}]),
Outcome;
-multi_commit(sync_sym_trans, Tid, CR, Store) ->
+multi_commit(sync_sym_trans, _Maj = [], Tid, CR, Store) ->
%% This protocol is the same as sym_trans except that it
%% uses syncronized calls to disk_log and syncronized commits
%% when several nodes are involved.
@@ -1408,7 +1450,7 @@ multi_commit(sync_sym_trans, Tid, CR, Store) ->
[{tid, Tid}, {outcome, Outcome}]),
Outcome;
-multi_commit(asym_trans, Tid, CR, Store) ->
+multi_commit(asym_trans, Majority, Tid, CR, Store) ->
%% This more expensive commit protocol is used when
%% table definitions are changed (schema transactions).
%% It is also used when the involved tables are
@@ -1469,6 +1511,10 @@ multi_commit(asym_trans, Tid, CR, Store) ->
{D2, CR2} = commit_decision(D, CR, [], []),
DiscNs = D2#decision.disc_nodes,
RamNs = D2#decision.ram_nodes,
+ case have_majority(Majority, DiscNs ++ RamNs) of
+ ok -> ok;
+ {error, Tab} -> mnesia:abort({no_majority, Tab})
+ end,
Pending = mnesia_checkpoint:tm_enter_pending(Tid, DiscNs, RamNs),
?ets_insert(Store, Pending),
{WaitFor, Local} = ask_commit(asym_trans, Tid, CR2, DiscNs, RamNs),
diff --git a/lib/mnesia/test/Makefile b/lib/mnesia/test/Makefile
index b165924ef2..ae4c9626c7 100644
--- a/lib/mnesia/test/Makefile
+++ b/lib/mnesia/test/Makefile
@@ -42,6 +42,7 @@ MODULES= \
mnesia_dirty_access_test \
mnesia_atomicity_test \
mnesia_consistency_test \
+ mnesia_majority_test \
mnesia_isolation_test \
mnesia_durability_test \
mnesia_recovery_test \
diff --git a/lib/mnesia/test/mnesia_SUITE.erl b/lib/mnesia/test/mnesia_SUITE.erl
index 8ba8427213..dc8f216c1c 100644
--- a/lib/mnesia/test/mnesia_SUITE.erl
+++ b/lib/mnesia/test/mnesia_SUITE.erl
@@ -142,6 +142,105 @@ silly() ->
mnesia_install_test:silly().
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+light(doc) ->
+ ["The 'light' test suite runs a selected set of test suites and is",
+ "intended to be the smallest test suite that is meaningful",
+ "to run. It starts with an installation test (which in essence is the",
+ "'silly' test case) and then it covers all functions in the API in",
+ "various depths. All configuration parameters and examples are also",
+ "covered."];
+light(suite) ->
+ [
+ install,
+ nice,
+ evil,
+ {mnesia_frag_test, light},
+ qlc,
+ registry,
+ config,
+ examples
+ ].
+
+install(suite) ->
+ [{mnesia_install_test, all}].
+
+nice(suite) ->
+ [{mnesia_nice_coverage_test, all}].
+
+evil(suite) ->
+ [{mnesia_evil_coverage_test, all}].
+
+qlc(suite) ->
+ [{mnesia_qlc_test, all}].
+
+registry(suite) ->
+ [{mnesia_registry_test, all}].
+
+config(suite) ->
+ [{mnesia_config_test, all}].
+
+examples(suite) ->
+ [{mnesia_examples_test, all}].
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+medium(doc) ->
+ ["The 'medium' test suite verfies the ACID (atomicity, consistency",
+ "isolation and durability) properties and various recovery scenarios",
+ "These tests may take quite while to run."];
+medium(suite) ->
+ [
+ install,
+ atomicity,
+ isolation,
+ durability,
+ recovery,
+ consistency,
+ majority,
+ {mnesia_frag_test, medium}
+ ].
+
+atomicity(suite) ->
+ [{mnesia_atomicity_test, all}].
+
+isolation(suite) ->
+ [{mnesia_isolation_test, all}].
+
+durability(suite) ->
+ [{mnesia_durability_test, all}].
+
+recovery(suite) ->
+ [{mnesia_recovery_test, all}].
+
+consistency(suite) ->
+ [{mnesia_consistency_test, all}].
+
+majority(suite) ->
+ [{mnesia_majority_test, all}].
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+heavy(doc) ->
+ ["The 'heavy' test suite runs some resource consuming tests and",
+ "benchmarks"];
+heavy(suite) ->
+ [measure].
+
+measure(suite) ->
+ [{mnesia_measure_test, all}].
+
+prediction(suite) ->
+ [{mnesia_measure_test, prediction}].
+
+fairness(suite) ->
+ [{mnesia_measure_test, fairness}].
+
+benchmarks(suite) ->
+ [{mnesia_measure_test, benchmarks}].
+
+consumption(suite) ->
+ [{mnesia_measure_test, consumption}].
+
+scalability(suite) ->
+ [{mnesia_measure_test, scalability}].
clean_up_suite(doc) -> ["Not a test case only kills mnesia and nodes, that where"
"started during the tests"];
diff --git a/lib/mnesia/test/mnesia_majority_test.erl b/lib/mnesia/test/mnesia_majority_test.erl
new file mode 100644
index 0000000000..17d1d8bcdd
--- /dev/null
+++ b/lib/mnesia/test/mnesia_majority_test.erl
@@ -0,0 +1,188 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1996-2009. 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(mnesia_majority_test).
+-author('[email protected]').
+-compile(export_all).
+-include("mnesia_test_lib.hrl").
+
+init_per_testcase(Func, Conf) ->
+ mnesia_test_lib:init_per_testcase(Func, Conf).
+
+fin_per_testcase(Func, Conf) ->
+ mnesia_test_lib:fin_per_testcase(Func, Conf).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+all(doc) ->
+ ["Verify that majority checking works"];
+all(suite) ->
+ [
+ write
+ , wread
+ , delete
+ , clear_table
+ , frag
+ , change_majority
+ , frag_change_majority
+ ].
+
+write(suite) -> [];
+write(Config) when is_list(Config) ->
+ [N1, N2, N3] = Nodes = ?acquire_nodes(3, Config),
+ Tab = t,
+ Schema = [{name, Tab}, {ram_copies, [N1,N2,N3]}, {majority,true}],
+ ?match({atomic, ok}, mnesia:create_table(Schema)),
+ ?match({[ok,ok,ok],[]},
+ rpc:multicall([N1,N2,N3], mnesia, wait_for_tables, [[Tab], 3000])),
+ ?match({atomic,ok},
+ mnesia:transaction(fun() -> mnesia:write({t,1,a}) end)),
+ mnesia_test_lib:kill_mnesia([N3]),
+ ?match({atomic,ok},
+ mnesia:transaction(fun() -> mnesia:write({t,1,a}) end)),
+ mnesia_test_lib:kill_mnesia([N2]),
+ ?match({aborted,{no_majority,Tab}},
+ mnesia:transaction(fun() -> mnesia:write({t,1,a}) end)).
+
+wread(suite) -> [];
+wread(Config) when is_list(Config) ->
+ [N1, N2] = Nodes = ?acquire_nodes(2, Config),
+ Tab = t,
+ Schema = [{name, Tab}, {ram_copies, [N1,N2]}, {majority,true}],
+ ?match({atomic, ok}, mnesia:create_table(Schema)),
+ ?match({[ok,ok],[]},
+ rpc:multicall([N1,N2], mnesia, wait_for_tables, [[Tab], 3000])),
+ ?match({atomic,[]},
+ mnesia:transaction(fun() -> mnesia:read(t,1,write) end)),
+ mnesia_test_lib:kill_mnesia([N2]),
+ ?match({aborted,{no_majority,Tab}},
+ mnesia:transaction(fun() -> mnesia:read(t,1,write) end)).
+
+delete(suite) -> [];
+delete(Config) when is_list(Config) ->
+ [N1, N2] = Nodes = ?acquire_nodes(2, Config),
+ Tab = t,
+ Schema = [{name, Tab}, {ram_copies, [N1,N2]}, {majority,true}],
+ ?match({atomic, ok}, mnesia:create_table(Schema)),
+ ?match({[ok,ok],[]},
+ rpc:multicall([N1,N2], mnesia, wait_for_tables, [[Tab], 3000])),
+ %% works as expected with majority of nodes present
+ ?match({atomic,ok},
+ mnesia:transaction(fun() -> mnesia:write({t,1,a}) end)),
+ ?match({atomic,ok},
+ mnesia:transaction(fun() -> mnesia:delete({t,1}) end)),
+ ?match({atomic,[]},
+ mnesia:transaction(fun() -> mnesia:read({t,1}) end)),
+ %% put the record back
+ ?match({atomic,ok},
+ mnesia:transaction(fun() -> mnesia:write({t,1,a}) end)),
+ ?match({atomic,[{t,1,a}]},
+ mnesia:transaction(fun() -> mnesia:read({t,1}) end)),
+ mnesia_test_lib:kill_mnesia([N2]),
+ ?match({aborted,{no_majority,Tab}},
+ mnesia:transaction(fun() -> mnesia:delete({t,1}) end)).
+
+clear_table(suite) -> [];
+clear_table(Config) when is_list(Config) ->
+ [N1, N2] = Nodes = ?acquire_nodes(2, Config),
+ Tab = t,
+ Schema = [{name, Tab}, {ram_copies, [N1,N2]}, {majority,true}],
+ ?match({atomic, ok}, mnesia:create_table(Schema)),
+ ?match({[ok,ok],[]},
+ rpc:multicall([N1,N2], mnesia, wait_for_tables, [[Tab], 3000])),
+ %% works as expected with majority of nodes present
+ ?match({atomic,ok},
+ mnesia:transaction(fun() -> mnesia:write({t,1,a}) end)),
+ ?match({atomic,ok}, mnesia:clear_table(t)),
+ ?match({atomic,[]},
+ mnesia:transaction(fun() -> mnesia:read({t,1}) end)),
+ %% put the record back
+ ?match({atomic,ok},
+ mnesia:transaction(fun() -> mnesia:write({t,1,a}) end)),
+ ?match({atomic,[{t,1,a}]},
+ mnesia:transaction(fun() -> mnesia:read({t,1}) end)),
+ mnesia_test_lib:kill_mnesia([N2]),
+ ?match({aborted,{no_majority,Tab}}, mnesia:clear_table(t)).
+
+frag(suite) -> [];
+frag(Config) when is_list(Config) ->
+ [N1] = Nodes = ?acquire_nodes(1, Config),
+ Tab = t,
+ Schema = [
+ {name, Tab}, {ram_copies, [N1]},
+ {majority,true},
+ {frag_properties, [{n_fragments, 2}]}
+ ],
+ ?match({atomic, ok}, mnesia:create_table(Schema)),
+ ?match(true, mnesia:table_info(t, majority)),
+ ?match(true, mnesia:table_info(t_frag2, majority)).
+
+change_majority(suite) -> [];
+change_majority(Config) when is_list(Config) ->
+ [N1,N2] = Nodes = ?acquire_nodes(2, Config),
+ Tab = t,
+ Schema = [
+ {name, Tab}, {ram_copies, [N1,N2]},
+ {majority,false}
+ ],
+ ?match({atomic, ok}, mnesia:create_table(Schema)),
+ ?match(false, mnesia:table_info(t, majority)),
+ ?match({atomic, ok},
+ mnesia:change_table_majority(t, true)),
+ ?match(true, mnesia:table_info(t, majority)),
+ ?match(ok,
+ mnesia:activity(transaction, fun() ->
+ mnesia:write({t,1,a})
+ end)),
+ mnesia_test_lib:kill_mnesia([N2]),
+ ?match({'EXIT',{aborted,{no_majority,_}}},
+ mnesia:activity(transaction, fun() ->
+ mnesia:write({t,1,a})
+ end)).
+
+frag_change_majority(suite) -> [];
+frag_change_majority(Config) when is_list(Config) ->
+ [N1,N2] = Nodes = ?acquire_nodes(2, Config),
+ Tab = t,
+ Schema = [
+ {name, Tab}, {ram_copies, [N1,N2]},
+ {majority,false},
+ {frag_properties,
+ [{n_fragments, 2},
+ {n_ram_copies, 2},
+ {node_pool, [N1,N2]}]}
+ ],
+ ?match({atomic, ok}, mnesia:create_table(Schema)),
+ ?match(false, mnesia:table_info(t, majority)),
+ ?match(false, mnesia:table_info(t_frag2, majority)),
+ ?match({aborted,{bad_type,t_frag2}},
+ mnesia:change_table_majority(t_frag2, true)),
+ ?match({atomic, ok},
+ mnesia:change_table_majority(t, true)),
+ ?match(true, mnesia:table_info(t, majority)),
+ ?match(true, mnesia:table_info(t_frag2, majority)),
+ ?match(ok,
+ mnesia:activity(transaction, fun() ->
+ mnesia:write({t,1,a})
+ end, mnesia_frag)),
+ mnesia_test_lib:kill_mnesia([N2]),
+ ?match({'EXIT',{aborted,{no_majority,_}}},
+ mnesia:activity(transaction, fun() ->
+ mnesia:write({t,1,a})
+ end, mnesia_frag)).
diff --git a/lib/public_key/doc/src/Makefile b/lib/public_key/doc/src/Makefile
index 298c28a740..afb17399da 100644
--- a/lib/public_key/doc/src/Makefile
+++ b/lib/public_key/doc/src/Makefile
@@ -1,7 +1,7 @@
#
# %CopyrightBegin%
#
-# Copyright Ericsson AB 2008-2010. All Rights Reserved.
+# Copyright Ericsson AB 2008-2011. All Rights Reserved.
#
# The contents of this file are subject to the Erlang Public License,
# Version 1.1, (the "License"); you may not use this file except in
@@ -52,6 +52,7 @@ XML_CHAPTER_FILES = \
introduction.xml \
public_key_records.xml \
cert_records.xml \
+ using_public_key.xml \
notes.xml
BOOK_FILES = book.xml
diff --git a/lib/public_key/doc/src/part.xml b/lib/public_key/doc/src/part.xml
index c338a71613..ea3123b5bd 100644
--- a/lib/public_key/doc/src/part.xml
+++ b/lib/public_key/doc/src/part.xml
@@ -1,4 +1,4 @@
-<?xml version="1.0" encoding="latin1" ?>
+<?xml version="1.0" encoding="iso-8859-1" ?>
<!DOCTYPE part SYSTEM "part.dtd">
<part xmlns:xi="http://www.w3.org/2001/XInclude">
@@ -38,5 +38,6 @@
<xi:include href="introduction.xml"/>
<xi:include href="public_key_records.xml"/>
<xi:include href="cert_records.xml"/>
+ <xi:include href="using_public_key.xml"/>
</part>
diff --git a/lib/public_key/doc/src/public_key.xml b/lib/public_key/doc/src/public_key.xml
index c5f57214b1..d60d91cd83 100644
--- a/lib/public_key/doc/src/public_key.xml
+++ b/lib/public_key/doc/src/public_key.xml
@@ -43,7 +43,7 @@
<note><p>All records used in this manual
<!-- except #policy_tree_node{} -->
- are generated from asn1 specifications
+ are generated from ASN.1 specifications
and are documented in the User's Guide. See <seealso
marker="public_key_records">Public key records</seealso> and <seealso
marker="cert_records">X.509 Certificate records</seealso>.
@@ -150,12 +150,12 @@
<fsummary> Decodes a public key asn1 der encoded entity.</fsummary>
<type>
<v>Asn1Type = atom() -</v>
- <d> Asn1 type present in the public_key applications
+ <d> ASN.1 type present in the public_key applications
asn1 specifications.</d>
<v>Der = der_encoded()</v>
</type>
<desc>
- <p> Decodes a public key asn1 der encoded entity.</p>
+ <p> Decodes a public key ASN.1 der encoded entity.</p>
</desc>
</func>
@@ -165,25 +165,25 @@
<type>
<v>Asn1Type = atom()</v>
<d> Asn1 type present in the public_key applications
- asn1 specifications.</d>
+ ASN.1 specifications.</d>
<v>Entity = term() - The erlang representation of <c> Asn1Type</c></v>
</type>
<desc>
- <p> Encodes a public key entity with asn1 DER encoding.</p>
+ <p> Encodes a public key entity with ASN.1 DER encoding.</p>
</desc>
</func>
<func>
<name>pem_decode(PemBin) -> [pem_entry()]</name>
<fsummary>Decode PEM binary data and return
- entries as asn1 der encoded entities. </fsummary>
+ entries as ASN.1 der encoded entities. </fsummary>
<type>
<v>PemBin = binary()</v>
<d>Example {ok, PemBin} = file:read_file("cert.pem").</d>
</type>
<desc>
<p>Decode PEM binary data and return
- entries as asn1 der encoded entities.</p>
+ entries as ASN.1 der encoded entities.</p>
</desc>
</func>
@@ -258,13 +258,13 @@
<func>
<name>pkix_decode_cert(Cert, otp|plain) -> #'Certificate'{} | #'OTPCertificate'{}</name>
- <fsummary> Decodes an asn1 der encoded pkix x509 certificate.</fsummary>
+ <fsummary> Decodes an ASN.1 der encoded pkix x509 certificate.</fsummary>
<type>
<v>Cert = der_encoded()</v>
</type>
<desc>
- <p>Decodes an asn1 der encoded pkix certificate. The otp option
- will use the customized asn1 specification OTP-PKIX.asn1 for
+ <p>Decodes an ASN.1 der encoded pkix certificate. The otp option
+ will use the customized ASN.1 specification OTP-PKIX.asn1 for
decoding and also recursively decode most of the standard
parts.</p>
</desc>
@@ -276,7 +276,7 @@
certificate.</fsummary>
<type>
<v>Asn1Type = atom()</v>
- <d>The asn1 type can be 'Certificate', 'OTPCertificate' or a subtype of either .</d>
+ <d>The ASN.1 type can be 'Certificate', 'OTPCertificate' or a subtype of either .</d>
</type>
<desc>
<p>Der encodes a pkix x509 certificate or part of such a
@@ -394,7 +394,7 @@
signed or in the case that digest type is <c>none</c>
it is the hashed value of "plain text" i.e. the digest.</d>
<v>DigestType = rsa_digest_type() | dsa_digest_type()</v>
- <v>Key = rsa_public_key() | dsa_public_key()</v>
+ <v>Key = rsa_private_key() | dsa_private_key()</v>
</type>
<desc>
<p> Creates a digital signature.</p>
diff --git a/lib/public_key/doc/src/using_public_key.xml b/lib/public_key/doc/src/using_public_key.xml
new file mode 100644
index 0000000000..f0eaeb8654
--- /dev/null
+++ b/lib/public_key/doc/src/using_public_key.xml
@@ -0,0 +1,504 @@
+<?xml version="1.0" encoding="iso-8859-1" ?>
+<!DOCTYPE chapter SYSTEM "chapter.dtd">
+
+<chapter>
+ <header>
+ <copyright>
+ <year>2011</year><year>2011</year>
+ <holder>Ericsson AB. All Rights Reserved.</holder>
+ </copyright>
+ <legalnotice>
+ 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.
+
+ </legalnotice>
+
+ <title>Using the public_key API</title>
+ <file>using_public_key.xml</file>
+ </header>
+
+ <section>
+ <title>General information</title>
+
+ <p> This chapter is dedicated to showing some
+ examples of how to use the public_key API. Keys and certificates
+ used in the following sections are generated only for the purpose
+ of testing the public key application.</p>
+
+ <p>Note that some shell printouts, in the following examples,
+ have been abbreviated for increased readability.</p>
+
+ </section>
+
+ <section>
+ <title>PEM files</title>
+ <p> Pulic key data (keys, certificates etc) may be stored in PEM format. PEM files
+ comes from the Private Enhanced Mail Internet standard and has a
+ structure that looks like this:</p>
+
+ <code>&lt;text&gt;
+ -----BEGIN &lt;SOMETHING&gt;-----
+ &lt;Attribute&gt; : &lt;Value&gt;
+ &lt;Base64 encoded DER data&gt;
+ -----END &lt;SOMETHING&gt;-----
+ &lt;text&gt;</code>
+
+ <p>A file can contain several BEGIN/END blocks. Text lines between
+ blocks are ignored. Attributes, if present, are currently ignored except
+ for <c>Proc-Type</c> and <c>DEK-Info</c> that are used when the DER data is
+ encrypted.</p>
+
+ <section>
+ <title>DSA private key</title>
+
+ <p>Note file handling is not done by the public_key application. </p>
+ <code>1> {ok, PemBin} = file:read_file("dsa.pem").
+{ok,&lt;&lt;"-----BEGIN DSA PRIVATE KEY-----\nMIIBuw"...&gt;&gt;}</code>
+
+ <p>This PEM file only has one entry a private DSA key.</p>
+ <code>2> [DSAEntry] = public_key:pem_decode(PemBin).
+[{'DSAPrivateKey',&lt;&lt;48,130,1,187,2,1,0,2,129,129,0,183,
+ 179,230,217,37,99,144,157,21,228,204,
+ 162,207,61,246,...&gt;&gt;,
+ not_encrypted}]</code>
+
+ <code>3> Key = public_key:pem_entry_decode(DSAEntry).
+#'DSAPrivateKey'{version = 0,
+ p = 12900045185019966618...6593,
+ q = 1216700114794736143432235288305776850295620488937,
+ g = 10442040227452349332...47213,
+ y = 87256807980030509074...403143,
+ x = 510968529856012146351317363807366575075645839654}</code>
+ </section>
+
+ <section>
+ <title>RSA private key encrypted with a password.</title>
+
+ <code>1> {ok, PemBin} = file:read_file("rsa.pem").
+{ok,&lt;&lt;"Bag Attribut"...&gt;&gt;}</code>
+
+ <p>This PEM file only has one entry a private RSA key.</p>
+ <code>2>[RSAEntry] = public_key:pem_decode(PemBin).
+[{'RSAPrivateKey',&lt;&lt;224,108,117,203,152,40,15,77,128,126,
+ 221,195,154,249,85,208,202,251,109,
+ 119,120,57,29,89,19,9,...&gt;&gt;,
+ {"DES-EDE3-CBC",&lt;&lt;"k�e��p�L"&gt;&gt;}}]
+
+ </code>
+
+ <p>In this example the password is "abcd1234".</p>
+ <code>3> Key = public_key:pem_entry_decode(RSAEntry, "abcd1234").
+ #'RSAPrivateKey'{version = 'two-prime',
+ modulus = 1112355156729921663373...2737107,
+ publicExponent = 65537,
+ privateExponent = 58064406231183...2239766033,
+ prime1 = 11034766614656598484098...7326883017,
+ prime2 = 10080459293561036618240...77738643771,
+ exponent1 = 77928819327425934607...22152984217,
+ exponent2 = 36287623121853605733...20588523793,
+ coefficient = 924840412626098444...41820968343,
+ otherPrimeInfos = asn1_NOVALUE}</code>
+ </section>
+
+ <section>
+ <title>X509 Certificates</title>
+
+ <code>1> {ok, PemBin} = file:read_file("cacerts.pem").
+{ok,&lt;&lt;"-----BEGIN CERTIFICATE-----\nMIIC7jCCAl"...&gt;&gt;}</code>
+
+ <p>This file includes two certificates</p>
+ <code>2> [CertEntry1, CertEntry2] = public_key:pem_decode(PemBin).
+[{'Certificate',&lt;&lt;48,130,2,238,48,130,2,87,160,3,2,1,2,2,
+ 9,0,230,145,97,214,191,2,120,150,48,13,
+ ...&gt;&gt;,
+ not_encrypted},
+ {'Certificate',&lt;&lt;48,130,3,200,48,130,3,49,160,3,2,1,2,2,1,
+ 1,48,13,6,9,42,134,72,134,247,...&gt;&gt;>,
+ not_encrypted}]</code>
+
+ <p>Certificates may of course be decoded as usual ... </p>
+ <code>2> Cert = public_key:pem_entry_decode(CertEntry1).
+#'Certificate'{
+ tbsCertificate =
+ #'TBSCertificate'{
+ version = v3,serialNumber = 16614168075301976214,
+ signature =
+ #'AlgorithmIdentifier'{
+ algorithm = {1,2,840,113549,1,1,5},
+ parameters = &lt;&lt;5,0&gt;&gt;},
+ issuer =
+ {rdnSequence,
+ [[#'AttributeTypeAndValue'{
+ type = {2,5,4,3},
+ value = &lt;&lt;19,8,101,114,108,97,110,103,67,65&gt;&gt;}],
+ [#'AttributeTypeAndValue'{
+ type = {2,5,4,11},
+ value = &lt;&lt;19,10,69,114,108,97,110,103,32,79,84,80&gt;&gt;}],
+ [#'AttributeTypeAndValue'{
+ type = {2,5,4,10},
+ value = &lt;&lt;19,11,69,114,105,99,115,115,111,110,32,65,66&gt;&gt;}],
+ [#'AttributeTypeAndValue'{
+ type = {2,5,4,7},
+ value = &lt;&lt;19,9,83,116,111,99,107,104,111,108,109&gt;&gt;}],
+ [#'AttributeTypeAndValue'{
+ type = {2,5,4,6},
+ value = &lt;&lt;19,2,83,69&gt;&gt;}],
+ [#'AttributeTypeAndValue'{
+ type = {1,2,840,113549,1,9,1},
+ value = &lt;&lt;22,22,112,101,116,101,114,64,101,114,...&gt;&gt;}]]},
+ validity =
+ #'Validity'{
+ notBefore = {utcTime,"080109082929Z"},
+ notAfter = {utcTime,"080208082929Z"}},
+ subject =
+ {rdnSequence,
+ [[#'AttributeTypeAndValue'{
+ type = {2,5,4,3},
+ value = &lt;&lt;19,8,101,114,108,97,110,103,67,65&gt;&gt;}],
+ [#'AttributeTypeAndValue'{
+ type = {2,5,4,11},
+ value = &lt;&lt;19,10,69,114,108,97,110,103,32,79,84,80&gt;&gt;}],
+ [#'AttributeTypeAndValue'{
+ type = {2,5,4,10},
+ value = &lt;&lt;19,11,69,114,105,99,115,115,111,110,32,...&gt;&gt;}],
+ [#'AttributeTypeAndValue'{
+ type = {2,5,4,7},
+ value = &lt;&lt;19,9,83,116,111,99,107,104,111,108,...&gt;&gt;}],
+ [#'AttributeTypeAndValue'{
+ type = {2,5,4,6},
+ value = &lt;&lt;19,2,83,69&gt;&gt;}],
+ [#'AttributeTypeAndValue'{
+ type = {1,2,840,113549,1,9,1},
+ value = &lt;&lt;22,22,112,101,116,101,114,64,...&gt;&gt;}]]},
+ subjectPublicKeyInfo =
+ #'SubjectPublicKeyInfo'{
+ algorithm =
+ #'AlgorithmIdentifier'{
+ algorithm = {1,2,840,113549,1,1,1},
+ parameters = &lt;&lt;5,0&gt;&gt;},
+ subjectPublicKey =
+ {0,&lt;&lt;48,129,137,2,129,129,0,203,209,187,77,73,231,90,...&gt;&gt;}},
+ issuerUniqueID = asn1_NOVALUE,
+ subjectUniqueID = asn1_NOVALUE,
+ extensions =
+ [#'Extension'{
+ extnID = {2,5,29,19},
+ critical = true,
+ extnValue = [48,3,1,1,255]},
+ #'Extension'{
+ extnID = {2,5,29,15},
+ critical = false,
+ extnValue = [3,2,1,6]},
+ #'Extension'{
+ extnID = {2,5,29,14},
+ critical = false,
+ extnValue = [4,20,27,217,65,152,6,30,142|...]},
+ #'Extension'{
+ extnID = {2,5,29,17},
+ critical = false,
+ extnValue = [48,24,129,22,112,101,116,101|...]}]},
+ signatureAlgorithm =
+ #'AlgorithmIdentifier'{
+ algorithm = {1,2,840,113549,1,1,5},
+ parameters = &lt;&lt;5,0&gt;&gt;},
+ signature =
+ {0,
+ &lt;&lt;163,186,7,163,216,152,63,47,154,234,139,73,154,96,120,
+ 165,2,52,196,195,109,167,192,...&gt;&gt;}}
+</code>
+
+ <p> Parts of certificates can be decoded with
+ public_key:der_decode/2 using that parts ASN.1 type.
+ Although application specific certificate
+ extension requires application specific ASN.1 decode/encode-functions.
+ Example, the first value of the rdnSequence above is of ASN.1 type
+ 'X520CommonName'. ({2,5,4,3} = ?id-at-commonName)</p>
+
+ <code>public_key:der_decode('X520CommonName', &lt;&lt;19,8,101,114,108,97,110,103,67,65&gt;&gt;).
+{printableString,"erlangCA"}</code>
+
+ <p>... but certificates can also be decode using the pkix_decode_cert/2 that
+ can customize and recursively decode standard parts of a certificate.</p>
+ <code>3>{_, DerCert, _} = CertEntry1.</code>
+ <code>4> public_key:pkix_decode_cert(DerCert, otp).
+#'OTPCertificate'{
+ tbsCertificate =
+ #'OTPTBSCertificate'{
+ version = v3,serialNumber = 16614168075301976214,
+ signature =
+ #'SignatureAlgorithm'{
+ algorithm = {1,2,840,113549,1,1,5},
+ parameters = 'NULL'},
+ issuer =
+ {rdnSequence,
+ [[#'AttributeTypeAndValue'{
+ type = {2,5,4,3},
+ value = {printableString,"erlangCA"}}],
+ [#'AttributeTypeAndValue'{
+ type = {2,5,4,11},
+ value = {printableString,"Erlang OTP"}}],
+ [#'AttributeTypeAndValue'{
+ type = {2,5,4,10},
+ value = {printableString,"Ericsson AB"}}],
+ [#'AttributeTypeAndValue'{
+ type = {2,5,4,7},
+ value = {printableString,"Stockholm"}}],
+ [#'AttributeTypeAndValue'{type = {2,5,4,6},value = "SE"}],
+ [#'AttributeTypeAndValue'{
+ type = {1,2,840,113549,1,9,1},
+ value = "[email protected]"}]]},
+ validity =
+ #'Validity'{
+ notBefore = {utcTime,"080109082929Z"},
+ notAfter = {utcTime,"080208082929Z"}},
+ subject =
+ {rdnSequence,
+ [[#'AttributeTypeAndValue'{
+ type = {2,5,4,3},
+ value = {printableString,"erlangCA"}}],
+ [#'AttributeTypeAndValue'{
+ type = {2,5,4,11},
+ value = {printableString,"Erlang OTP"}}],
+ [#'AttributeTypeAndValue'{
+ type = {2,5,4,10},
+ value = {printableString,"Ericsson AB"}}],
+ [#'AttributeTypeAndValue'{
+ type = {2,5,4,7},
+ value = {printableString,"Stockholm"}}],
+ [#'AttributeTypeAndValue'{type = {2,5,4,6},value = "SE"}],
+ [#'AttributeTypeAndValue'{
+ type = {1,2,840,113549,1,9,1},
+ value = "[email protected]"}]]},
+ subjectPublicKeyInfo =
+ #'OTPSubjectPublicKeyInfo'{
+ algorithm =
+ #'PublicKeyAlgorithm'{
+ algorithm = {1,2,840,113549,1,1,1},
+ parameters = 'NULL'},
+ subjectPublicKey =
+ #'RSAPublicKey'{
+ modulus =
+ 1431267547247997...37419,
+ publicExponent = 65537}},
+ issuerUniqueID = asn1_NOVALUE,
+ subjectUniqueID = asn1_NOVALUE,
+ extensions =
+ [#'Extension'{
+ extnID = {2,5,29,19},
+ critical = true,
+ extnValue =
+ #'BasicConstraints'{
+ cA = true,pathLenConstraint = asn1_NOVALUE}},
+ #'Extension'{
+ extnID = {2,5,29,15},
+ critical = false,
+ extnValue = [keyCertSign,cRLSign]},
+ #'Extension'{
+ extnID = {2,5,29,14},
+ critical = false,
+ extnValue = [27,217,65,152,6,30,142,132,245|...]},
+ #'Extension'{
+ extnID = {2,5,29,17},
+ critical = false,
+ extnValue = [{rfc822Name,"[email protected]"}]}]},
+ signatureAlgorithm =
+ #'SignatureAlgorithm'{
+ algorithm = {1,2,840,113549,1,1,5},
+ parameters = 'NULL'},
+ signature =
+ {0,
+ &lt;&lt;163,186,7,163,216,152,63,47,154,234,139,73,154,96,120,
+ 165,2,52,196,195,109,167,192,...&gt;&gt;}}
+</code>
+
+ <p>This call is equivalent to public_key:pem_entry_decode(CertEntry1)</p>
+ <code>5> public_key:pkix_decode_cert(DerCert, plain).
+#'Certificate'{ ...}
+</code>
+ </section>
+
+ <section>
+ <title>Encoding public key data to PEM format</title>
+
+ <p>If you have public key data and and want to create a PEM file
+ you can do that by calling the functions
+ public_key:pem_entry_encode/2 and pem_encode/1 and then saving the
+ result to a file. For example assume you have PubKey =
+ 'RSAPublicKey'{} then you can create a PEM-"RSA PUBLIC KEY" file
+ (ASN.1 type 'RSAPublicKey') or a PEM-"PUBLIC KEY" file
+ ('SubjectPublicKeyInfo' ASN.1 type).</p>
+
+ <p> The second element of the PEM-entry will be the ASN.1 DER encoded
+ key data.</p>
+
+ <code>1> PemEntry = public_key:pem_entry_encode('RSAPublicKey', RSAPubKey).
+{'RSAPublicKey', &lt;&lt;48,72,...&gt;&gt;, not_encrypted}
+
+2> PemBin = public_key:pem_encode([PemEntry]).
+&lt;&lt;"-----BEGIN RSA PUBLIC KEY-----\nMEgC...&gt;&gt;
+
+3> file:write_file("rsa_pub_key.pem", PemBin).
+ok</code>
+
+ <p> or </p>
+
+ <code>1> PemBin = public_key:pem_entry_encode('SubjectPublicKeyInfo', RSAPubKey).
+{'SubjectPublicKeyInfo', &lt;&lt;48,92...&gt;&gt;, not_encrypted}
+
+2> PemBin = public_key:pem_encode([PemEntry]).
+&lt;&lt;"-----BEGIN PUBLIC KEY-----\nMFw...&gt;&gt;
+
+3> file:write_file("pub_key.pem", PemBin).
+ok</code>
+
+ </section>
+</section>
+
+<section>
+ <title>RSA public key cryptography </title>
+ <p> Suppose you have PrivateKey = #'RSAPrivateKey{}' and the
+ plaintext Msg = binary() and the corresponding public key
+ PublicKey = #'RSAPublicKey'{} then you can do the following.
+ Note that you normally will only do one of the encrypt or
+ decrypt operations and the peer will do the other.
+ </p>
+
+ <p>Encrypt with the private key </p>
+ <code>RsaEncrypted = public_key:encrypt_private(Msg, PrivateKey),
+Msg = public_key:decrypt_public(RsaEncrypted, PublicKey),</code>
+
+ <p>Encrypt with the public key </p>
+ <code>RsaEncrypted = public_key:encrypt_public(Msg, PublicKey),
+Msg = public_key:decrypt_private(RsaEncrypted, PrivateKey),</code>
+ </section>
+
+ <section>
+ <title>Digital signatures</title>
+
+ <p> Suppose you have PrivateKey = #'RSAPrivateKey{}'or
+ #'DSAPrivateKey'{} and the plaintext Msg = binary() and the
+ corresponding public key PublicKey = #'RSAPublicKey'{} or
+ {integer(), #'DssParams'{}} then you can do the following. Note
+ that you normally will only do one of the sign or verify operations
+ and the peer will do the other. </p>
+
+ <code>Signature = public_key:sign(Msg, sha, PrivateKey),
+true = public_key:verify(Msg, sha, Signature, PublicKey),</code>
+
+ <p>It might be appropriate to calculate the message digest before
+ calling sign or verify and then you can use the none as second
+ argument.</p>
+
+ <code>Digest = crypto:sha(Msg),
+Signature = public_key:sign(Digest, none, PrivateKey),
+true = public_key:verify(Digest, none, Signature, PublicKey),
+ </code>
+
+ </section>
+
+ <section>
+ <title>SSH files</title>
+
+ <p>SSH typically uses PEM files for private keys but has its
+ own file format for storing public keys. The erlang public_key
+ application can be used to parse the content of SSH public key files.</p>
+
+ <section>
+ <title> RFC 4716 SSH public key files </title>
+
+ <p>RFC 4716 SSH files looks confusingly like PEM files,
+ but there are some differences.</p>
+ <code>1> {ok, SshBin} = file:read_file("ssh2_rsa_pub").
+{ok, &lt;&lt;"---- BEGIN SSH2 PUBLIC KEY ----\nAAAA"...&gt;&gt;}</code>
+
+ <p>This is equivalent to calling public_key:ssh_decode(SshBin, rfc4716_public_key).
+ </p>
+ <code>2> public_key:ssh_decode(SshBin, public_key).
+[{#'RSAPublicKey'{modulus = 794430685...91663,
+ publicExponent = 35}, []}]
+</code>
+
+ </section>
+
+ <section>
+ <title> Openssh public key format </title>
+ <code>1> {ok, SshBin} = file:read_file("openssh_dsa_pub").
+{ok,&lt;&lt;"ssh-dss AAAAB3Nza"...&gt;&gt;}</code>
+
+ <p>This is equivalent to calling public_key:ssh_decode(SshBin, openssh_public_key).
+ </p>
+ <code>2> public_key:ssh_decode(SshBin, public_key).
+[{{15642692...694280725,
+ #'Dss-Parms'{p = 17291273936...696123221,
+ q = 1255626590179665817295475654204371833735706001853,
+ g = 10454211196...480338645}},
+ [{comment,"dhopson@VMUbuntu-DSH"}]}]
+</code>
+ </section>
+
+ <section>
+ <title> Known hosts - openssh format</title>
+
+ <code>1> {ok, SshBin} = file:read_file("known_hosts").
+{ok,&lt;&lt;"hostname.domain.com,192.168.0.1 ssh-rsa AAAAB...&gt;&gt;}</code>
+
+ <p>Returns a list of public keys and their related attributes
+ each pair of key and attributes corresponds to one entry in
+ the known hosts file.</p>
+
+ <code>2> public_key:ssh_decode(SshBin, known_hosts).
+[{#'RSAPublicKey'{modulus = 1498979460408...72721699,
+ publicExponent = 35},
+ [{hostnames,["hostname.domain.com","192.168.0.1"]}]},
+ {#'RSAPublicKey'{modulus = 14989794604088...2721699,
+ publicExponent = 35},
+ [{comment,"[email protected]"},
+ {hostnames,["|1|BWO5qDxk/cFH0wa05JLdHn+j6xQ=|rXQvIxh5cDD3C43k5DPDamawVNA="]}]}]
+</code>
+ </section>
+
+ <section>
+ <title> Authorized keys - openssh format</title>
+
+ <code>1> {ok, SshBin} = file:read_file("auth_keys").
+{ok, &lt;&lt;"command=\"dump /home\",no-pty,no-port-forwarding ssh-rsa AAA...&gt;&gt;}</code>
+
+ <p>Returns a list of public keys and their related attributes
+ each pair of key and attributes corresponds to one entry in
+ the authorized key file.</p>
+
+ <code>2> public_key:ssh_decode(SshBin, auth_keys).
+[{#'RSAPublicKey'{modulus = 794430685...691663,
+ publicExponent = 35},
+ [{comment,"dhopson@VMUbuntu-DSH"},
+ {options,["command=\"dump/home\"","no-pty",
+ "no-port-forwarding"]}]},
+ {{1564269258491...607694280725,
+ #'Dss-Parms'{p = 17291273936185...763696123221,
+ q = 1255626590179665817295475654204371833735706001853,
+ g = 10454211195705...60511039590076780999046480338645}},
+ [{comment,"dhopson@VMUbuntu-DSH"}]}]
+</code>
+ </section>
+
+ <section>
+ <title> Creating an SSH file from public key data </title>
+
+ <p>If you got a public key <c>PubKey</c> and a related list of
+ attributes <c>Attributes</c> as returned
+ by ssh_decode/2 you can create a new ssh file for example</p>
+ <code>N> SshBin = public_key:ssh_encode([{PubKey, Attributes}], openssh_public_key),
+&lt;&lt;"ssh-rsa "...&gt;&gt;
+N+1> file:write_file("id_rsa.pub", SshBin).
+ok</code>
+ </section>
+ </section>
+</chapter>
diff --git a/lib/reltool/src/reltool_server.erl b/lib/reltool/src/reltool_server.erl
index 9743289ca6..692baea0a4 100644
--- a/lib/reltool/src/reltool_server.erl
+++ b/lib/reltool/src/reltool_server.erl
@@ -465,16 +465,16 @@ analyse(#state{common = C,
[MissingApp2 | Apps2]
end,
app_propagate_is_used_by(C, Apps3),
- Apps4 = read_apps(C, Sys, Apps3, []),
+ {Apps4,Status4} = app_recap_dependencies(C, Sys, Apps3, [], Status3),
%% io:format("Missing app: ~p\n",
%% [lists:keysearch(?MISSING_APP_NAME, #app.name, Apps4)]),
Sys2 = Sys#sys{apps = Apps4},
- case verify_config(RelApps2, Sys2, Status3) of
- {ok, _Warnings} = Status4 ->
- {S#state{sys = Sys2}, Status4};
- {error, _} = Status4 ->
- {S, Status4}
+ case verify_config(RelApps2, Sys2, Status4) of
+ {ok, _Warnings} = Status5 ->
+ {S#state{sys = Sys2}, Status5};
+ {error, _} = Status5 ->
+ {S, Status5}
end.
apps_in_rels(Rels, Apps, Status) ->
@@ -548,21 +548,24 @@ app_init_is_included(C,
{derived, [_ | _]} -> % App is included in at least one rel
{true, undefined, true, Status}
end,
- A2 = A#app{is_pre_included = IsPreIncl,
+ {Mods2,Status3} = lists:mapfoldl(fun(Mod,Acc) ->
+ mod_init_is_included(C,
+ Mod,
+ ModCond,
+ AppCond,
+ Default,
+ Acc)
+ end,
+ Status2,
+ Mods),
+ A2 = A#app{mods = Mods2,
+ is_pre_included = IsPreIncl,
is_included = IsIncl,
rels = Rels},
ets:insert(C#common.app_tab, A2),
- lists:foreach(fun(Mod) ->
- mod_init_is_included(C,
- Mod,
- ModCond,
- AppCond,
- Default)
- end,
- Mods),
- {A2, Status2}.
-
-mod_init_is_included(C, M, ModCond, AppCond, Default) ->
+ {A2, Status3}.
+
+mod_init_is_included(C, M, ModCond, AppCond, Default, Status) ->
%% print(M#mod.name, hipe, "incl_cond -> ~p\n", [AppCond]),
IsIncl =
case AppCond of
@@ -595,9 +598,52 @@ mod_init_is_included(C, M, ModCond, AppCond, Default) ->
Default
end
end,
+
M2 = M#mod{is_pre_included = IsIncl, is_included = IsIncl},
+
+ Status2 =
+ case ets:lookup(C#common.mod_tab,M#mod.name) of
+ [Existing] ->
+ case {Existing#mod.is_included,IsIncl} of
+ {false,_} ->
+ Warning =
+ lists:concat(
+ ["Module ",M#mod.name,
+ " exists in applications ", Existing#mod.app_name,
+ " and ", M#mod.app_name,
+ ". Using module from application ",
+ M#mod.app_name, "."]),
+ ets:insert(C#common.mod_tab, M2),
+ reltool_utils:add_warning(Status,Warning);
+ {_,false} ->
+ Warning =
+ lists:concat(
+ ["Module ",M#mod.name,
+ " exists in applications ", Existing#mod.app_name,
+ " and ", M#mod.app_name,
+ ". Using module from application ",
+ Existing#mod.app_name, "."]),
+
+ %% Don't insert in mod_tab - using Existing
+ reltool_utils:add_warning(Status,Warning);
+ {_,_} ->
+ Error =
+ lists:concat(
+ ["Module ",M#mod.name,
+ " potentially included by ",
+ "two different applications: ",
+ Existing#mod.app_name, " and ",
+ M#mod.app_name, "."]),
+ %% Don't insert in mod_tab - using Existing
+ reltool_utils:return_first_error(Status,Error)
+ end;
+ [] ->
+ ets:insert(C#common.mod_tab, M2),
+ Status
+ end,
+
%% print(M#mod.name, hipe, "~p -> ~p\n", [M2, IsIncl]),
- ets:insert(C#common.mod_tab, M2).
+ {M2,Status2}.
false_to_undefined(Bool) ->
case Bool of
@@ -612,21 +658,27 @@ app_propagate_is_included(_C, _Sys, [], Acc) ->
Acc.
mod_propagate_is_included(C, Sys, A, [#mod{name = ModName} | Mods], Acc) ->
- [M2] = ets:lookup(C#common.mod_tab, ModName),
- %% print(ModName, file, "Maybe Prop ~p -> ~p\n",
- %% [M2, M2#mod.is_included]),
- %% print(ModName, filename, "Maybe Prop ~p -> ~p\n",
- %% [M2, M2#mod.is_included]),
Acc2 =
- case M2#mod.is_included of
- true ->
- %% Propagate include mark
- mod_mark_is_included(C, Sys, ModName, M2#mod.uses_mods, Acc);
- false ->
- Acc;
- undefined ->
- Acc
- end,
+ case ets:lookup(C#common.mod_tab, ModName) of
+ [M2] when M2#mod.app_name=:=A#app.name ->
+ %% print(ModName, file, "Maybe Prop ~p -> ~p\n",
+ %% [M2, M2#mod.is_included]),
+ %% print(ModName, filename, "Maybe Prop ~p -> ~p\n",
+ %% [M2, M2#mod.is_included]),
+ case M2#mod.is_included of
+ true ->
+ %% Propagate include mark
+ mod_mark_is_included(C,Sys,ModName,M2#mod.uses_mods,Acc);
+ false ->
+ Acc;
+ undefined ->
+ Acc
+ end;
+ [_] ->
+ %% This module is currently used from a different application
+ %% Ignore
+ Acc
+ end,
mod_propagate_is_included(C, Sys, A, Mods, Acc2);
mod_propagate_is_included(_C, _Sys, _A, [], Acc) ->
Acc.
@@ -740,9 +792,10 @@ mod_propagate_is_used_by(C, [#mod{name = ModName} | Mods]) ->
mod_propagate_is_used_by(_C, []) ->
ok.
-read_apps(C, Sys, [#app{mods = Mods, is_included = IsIncl} = A | Apps], Acc) ->
- {Mods2, IsIncl2} = read_apps(C, Sys, A, Mods, [], IsIncl),
- Status =
+app_recap_dependencies(C, Sys, [#app{mods = Mods, is_included = IsIncl} = A | Apps], Acc, Status) ->
+ {Mods2, IsIncl2, Status2} =
+ mod_recap_dependencies(C, Sys, A, Mods, [], IsIncl, Status),
+ AppStatus =
case lists:keymember(missing, #mod.status, Mods2) of
true -> missing;
false -> ok
@@ -759,34 +812,52 @@ read_apps(C, Sys, [#app{mods = Mods, is_included = IsIncl} = A | Apps], Acc) ->
UsedByApps2 = lists:usort(UsedByApps),
A2 = A#app{mods = Mods2,
- status = Status,
+ status = AppStatus,
uses_mods = UsesMods2,
used_by_mods = UsedByMods2,
uses_apps = UsesApps2,
used_by_apps = UsedByApps2,
is_included = IsIncl2},
- read_apps(C, Sys, Apps, [A2 | Acc]);
-read_apps(_C, _Sys, [], Acc) ->
- lists:reverse(Acc).
-
-read_apps(C, Sys, A, [#mod{name = ModName} | Mods], Acc, IsIncl) ->
- [M2] = ets:lookup(C#common.mod_tab, ModName),
- Status = do_get_status(M2),
- %% print(M2#mod.name, hipe, "status -> ~p\n", [Status]),
- {IsIncl2, M3} =
- case M2#mod.is_included of
- true ->
- UsedByMods =
- [N || {_, N} <- ets:lookup(C#common.mod_used_by_tab,
- ModName)],
- {true, M2#mod{status = Status, used_by_mods = UsedByMods}};
- _ ->
- {IsIncl, M2#mod{status = Status, used_by_mods = []}}
- end,
- ets:insert(C#common.mod_tab, M3),
- read_apps(C, Sys, A, Mods, [M3 | Acc], IsIncl2);
-read_apps(_C, _Sys, _A, [], Acc, IsIncl) ->
- {lists:reverse(Acc), IsIncl}.
+ ets:insert(C#common.app_tab,A2),
+ app_recap_dependencies(C, Sys, Apps, [A2 | Acc], Status2);
+app_recap_dependencies(_C, _Sys, [], Acc, Status) ->
+ {lists:reverse(Acc), Status}.
+
+mod_recap_dependencies(C, Sys, A, [#mod{name = ModName}=M1 | Mods], Acc, IsIncl, Status) ->
+ case ets:lookup(C#common.mod_tab, ModName) of
+ [M2] when M2#mod.app_name=:=A#app.name ->
+ ModStatus = do_get_status(M2),
+ %% print(M2#mod.name, hipe, "status -> ~p\n", [ModStatus]),
+ {IsIncl2, M3} =
+ case M2#mod.is_included of
+ true ->
+ UsedByMods =
+ [N || {_, N} <- ets:lookup(C#common.mod_used_by_tab,
+ ModName)],
+ {true, M2#mod{status = ModStatus, used_by_mods = UsedByMods}};
+ _ ->
+ {IsIncl, M2#mod{status = ModStatus, used_by_mods = []}}
+ end,
+ ets:insert(C#common.mod_tab, M3),
+ mod_recap_dependencies(C, Sys, A, Mods, [M3 | Acc], IsIncl2, Status);
+ [_] when A#app.is_included==false; M1#mod.incl_cond==exclude ->
+ %% App is explicitely excluded so it is ok that the module
+ %% record does not exist for this module in this
+ %% application.
+ mod_recap_dependencies(C, Sys, A, Mods, [M1 | Acc], IsIncl, Status);
+ [M2] ->
+ %% A module is potensially included by multiple
+ %% applications. This is not allowed!
+ Error =
+ lists:concat(
+ ["Module ",ModName,
+ " potentially included by two different applications: ",
+ A#app.name, " and ", M2#mod.app_name, "."]),
+ Status2 = reltool_utils:return_first_error(Status,Error),
+ mod_recap_dependencies(C, Sys, A, Mods, [M1 | Acc], IsIncl, Status2)
+ end;
+mod_recap_dependencies(_C, _Sys, _A, [], Acc, IsIncl, Status) ->
+ {lists:reverse(Acc), IsIncl, Status}.
do_get_status(M) ->
if
diff --git a/lib/reltool/src/reltool_sys_win.erl b/lib/reltool/src/reltool_sys_win.erl
index dbb8e32aa2..76c064f1e7 100644
--- a/lib/reltool/src/reltool_sys_win.erl
+++ b/lib/reltool/src/reltool_sys_win.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2009-2010. All Rights Reserved.
+%% Copyright Ericsson AB 2009-2011. All Rights Reserved.
%%
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
@@ -1092,17 +1092,23 @@ move_app(S, {_ItemNo, AppBase}, Action) ->
OldApp#app{incl_cond = AppCond}.
do_set_app(#state{server_pid = ServerPid, app_wins = AppWins} = S, NewApp) ->
- {ok, AnalysedApp, Warnings} = reltool_server:set_app(ServerPid, NewApp),
+ Result = reltool_server:set_app(ServerPid, NewApp),
[ok = reltool_app_win:refresh(AW#app_win.pid) || AW <- AppWins],
S2 = redraw_apps(S),
- case Warnings of
- [] ->
- ignore;
- _ ->
- Msg = lists:flatten([[W, $\n] || W <- Warnings]),
- display_message(Msg, ?wxICON_WARNING)
- end,
- {ok, AnalysedApp, S2}.
+ ReturnApp =
+ case Result of
+ {ok, AnalysedApp, []} ->
+ AnalysedApp;
+ {ok, AnalysedApp, Warnings} ->
+ Msg = lists:flatten([[W, $\n] || W <- Warnings]),
+ display_message(Msg, ?wxICON_WARNING),
+ AnalysedApp;
+ {error, Reason} ->
+ display_message(Reason, ?wxICON_ERROR),
+ {ok,OldApp} = reltool_server:get_app(ServerPid, NewApp#app.name),
+ OldApp
+ end,
+ {ok, ReturnApp, S2}.
redraw_apps(#state{server_pid = ServerPid,
source = SourceCtrl,
diff --git a/lib/reltool/test/Makefile b/lib/reltool/test/Makefile
index 62fe05238b..767454b66a 100644
--- a/lib/reltool/test/Makefile
+++ b/lib/reltool/test/Makefile
@@ -76,8 +76,8 @@ release_tests_spec: opt
$(INSTALL_DATA) reltool.spec reltool.cover $(ERL_FILES) $(HRL_FILES) $(RELSYSDIR)
$(INSTALL_SCRIPT) rtt $(INSTALL_PROGS) $(RELSYSDIR)
$(INSTALL_DATA) $(INSTALL_PROGS) $(RELSYSDIR)
-# chmod -R u+w $(RELSYSDIR)
-# @tar cf - *_SUITE_data | (cd $(RELSYSDIR); tar xf -)
+ chmod -R u+w $(RELSYSDIR)
+ @tar cf - *_SUITE_data | (cd $(RELSYSDIR); tar xf -)
release_docs_spec:
diff --git a/lib/reltool/test/reltool_server_SUITE.erl b/lib/reltool/test/reltool_server_SUITE.erl
index b77560db94..9ed79e8c95 100644
--- a/lib/reltool/test/reltool_server_SUITE.erl
+++ b/lib/reltool/test/reltool_server_SUITE.erl
@@ -25,6 +25,7 @@
-compile(export_all).
-include("reltool_test_lib.hrl").
+-include_lib("common_test/include/ct.hrl").
-define(NODE_NAME, '__RELTOOL__TEMPORARY_TEST__NODE__').
-define(WORK_DIR, "reltool_work_dir").
@@ -53,7 +54,7 @@ all() ->
[start_server, set_config, create_release,
create_script, create_target, create_embedded,
create_standalone, create_old_target,
- otp_9135].
+ otp_9135, otp_9229_exclude_app, otp_9229_exclude_mod].
groups() ->
[].
@@ -361,6 +362,114 @@ create_old_target(_Config) ->
ok.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% OTP-9229 - handle duplicated module names, i.e. same module name
+%% exists in two applications.
+
+%% Include on app, exclude the other
+otp_9229_exclude_app(Config) ->
+ DataDir = ?config(data_dir,Config),
+ LibDir = filename:join(DataDir,"otp_9229"),
+
+ %% Configure the server
+ ExclApp =
+ {sys,
+ [
+ {root_dir, code:root_dir()},
+ {lib_dirs, [LibDir]},
+ {incl_cond,exclude},
+ {app,x,[{incl_cond,include}]},
+ {app,y,[{incl_cond,exclude}]},
+ {app,kernel,[{incl_cond,include}]},
+ {app,stdlib,[{incl_cond,include}]},
+ {app,sasl,[{incl_cond,include}]}
+ ]},
+
+ %% Generate target file
+ TargetDir = filename:join([?WORK_DIR, "target_dupl_mod_excl_app"]),
+ ?m(ok, reltool_utils:recursive_delete(TargetDir)),
+ ?m(ok, file:make_dir(TargetDir)),
+ ?log("SPEC: ~p\n", [reltool:get_target_spec([{config, ExclApp}])]),
+ {ok,["Module mylib exists in applications x and y. Using module from application x."]} = reltool:get_status([{config, ExclApp}]),
+ ?m(ok, reltool:create_target([{config, ExclApp}], TargetDir)),
+
+ Erl = filename:join([TargetDir, "bin", "erl"]),
+ {ok, Node} = ?msym({ok, _}, start_node(?NODE_NAME, Erl)),
+
+ AbsTargetDir = filename:absname(TargetDir),
+ XArchive = "x-1.0.ez",
+ AbsXArchive = filename:join([AbsTargetDir,lib,XArchive]),
+ XEbin = ["ebin","x-1.0",XArchive],
+ YArchive = "y-1.0.ez",
+ AbsYArchive = filename:join([AbsTargetDir,lib,YArchive]),
+
+ ?m(true, filelib:is_file(AbsXArchive)),
+ ?m(XEbin, mod_path(Node,x)),
+ ?m(XEbin, mod_path(Node,mylib)),
+ ?m(false, filelib:is_file(AbsYArchive)),
+ ?m(non_existing, mod_path(Node,y)),
+
+ ?msym(ok, stop_node(Node)),
+
+ ok.
+
+%% Include both apps, but exclude common module from one app
+otp_9229_exclude_mod(Config) ->
+ DataDir = ?config(data_dir,Config),
+ LibDir = filename:join(DataDir,"otp_9229"),
+
+ %% Configure the server
+ ExclMod =
+ {sys,
+ [
+ {root_dir, code:root_dir()},
+ {lib_dirs, [LibDir]},
+ {incl_cond,exclude},
+ {app,x,[{incl_cond,include}]},
+ {app,y,[{incl_cond,include},{mod, mylib,[{incl_cond,exclude}]}]},
+ {app,kernel,[{incl_cond,include}]},
+ {app,stdlib,[{incl_cond,include}]},
+ {app,sasl,[{incl_cond,include}]}
+ ]},
+
+ %% Generate target file
+ TargetDir = filename:join([?WORK_DIR, "target_dupl_mod_excl_mod"]),
+ ?m(ok, reltool_utils:recursive_delete(TargetDir)),
+ ?m(ok, file:make_dir(TargetDir)),
+ ?log("SPEC: ~p\n", [reltool:get_target_spec([{config, ExclMod}])]),
+ {ok,["Module mylib exists in applications x and y. Using module from application x."]} = reltool:get_status([{config, ExclMod}]),
+ ?m(ok, reltool:create_target([{config, ExclMod}], TargetDir)),
+
+ Erl = filename:join([TargetDir, "bin", "erl"]),
+ {ok, Node} = ?msym({ok, _}, start_node(?NODE_NAME, Erl)),
+
+ AbsTargetDir = filename:absname(TargetDir),
+ XArchive = "x-1.0.ez",
+ AbsXArchive = filename:join([AbsTargetDir,lib,XArchive]),
+ XEbin = ["ebin","x-1.0",XArchive],
+ YArchive = "y-1.0.ez",
+ AbsYArchive = filename:join([AbsTargetDir,lib,YArchive]),
+ YEbin = ["ebin","y-1.0",YArchive],
+
+ ?m(true, filelib:is_file(AbsXArchive)),
+ ?m(XEbin, mod_path(Node,x)),
+ ?m(XEbin, mod_path(Node,mylib)),
+ ?m(true, filelib:is_file(AbsYArchive)),
+ ?m(YEbin, mod_path(Node,y)),
+
+ %% Remove path to XEbin and check that mylib is not located in YEbin
+ Mylib = rpc:call(Node,code,which,[mylib]),
+ rpc:call(Node,code,del_path,[filename:dirname(Mylib)]),
+ ?m(non_existing, mod_path(Node,mylib)),
+
+ ?msym(ok, stop_node(Node)),
+
+ ok.
+
+
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Library functions
erl_libs() ->
@@ -407,6 +516,20 @@ os_cmd(Cmd) when is_list(Cmd) ->
end
end.
+%% Returns the location (directory) of the given module. Split,
+%% reverted and relative to the lib dir.
+mod_path(Node,Mod) ->
+ case rpc:call(Node,code,which,[Mod]) of
+ Path when is_list(Path) ->
+ lists:takewhile(
+ fun("lib") -> false;
+ (_) -> true
+ end,
+ lists:reverse(filename:split(filename:dirname(Path))));
+ Other ->
+ Other
+ end.
+
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Node handling
diff --git a/lib/reltool/test/reltool_server_SUITE_data/Makefile.src b/lib/reltool/test/reltool_server_SUITE_data/Makefile.src
new file mode 100644
index 0000000000..049e8dd6cc
--- /dev/null
+++ b/lib/reltool/test/reltool_server_SUITE_data/Makefile.src
@@ -0,0 +1,19 @@
+EFLAGS=+debug_info
+
+OTP9229= \
+ otp_9229/x-1.0/ebin/x.@EMULATOR@ \
+ otp_9229/x-1.0/ebin/mylib.@EMULATOR@ \
+ otp_9229/y-1.0/ebin/y.@EMULATOR@ \
+ otp_9229/y-1.0/ebin/mylib.@EMULATOR@
+
+
+all: $(OTP9229)
+
+otp_9229/x-1.0/ebin/x.@EMULATOR@: otp_9229/x-1.0/src/x.erl
+ erlc $(EFLAGS) -ootp_9229/x-1.0/ebin otp_9229/x-1.0/src/x.erl
+otp_9229/x-1.0/ebin/mylib.@EMULATOR@: otp_9229/x-1.0/src/mylib.erl
+ erlc $(EFLAGS) -ootp_9229/x-1.0/ebin otp_9229/x-1.0/src/mylib.erl
+otp_9229/y-1.0/ebin/y.@EMULATOR@: otp_9229/y-1.0/src/y.erl
+ erlc $(EFLAGS) -ootp_9229/y-1.0/ebin otp_9229/y-1.0/src/y.erl
+otp_9229/y-1.0/ebin/mylib.@EMULATOR@: otp_9229/y-1.0/src/mylib.erl
+ erlc $(EFLAGS) -ootp_9229/y-1.0/ebin otp_9229/y-1.0/src/mylib.erl
diff --git a/lib/reltool/test/reltool_server_SUITE_data/otp_9229/x-1.0/ebin/x.app b/lib/reltool/test/reltool_server_SUITE_data/otp_9229/x-1.0/ebin/x.app
new file mode 100644
index 0000000000..e597704b19
--- /dev/null
+++ b/lib/reltool/test/reltool_server_SUITE_data/otp_9229/x-1.0/ebin/x.app
@@ -0,0 +1,7 @@
+%% -*- erlang -*-
+{application, x,
+ [{description, "X CXC 138 11"},
+ {vsn, "1.0"},
+ {modules, [x, mylib]},
+ {registered, []},
+ {applications, [kernel, stdlib]}]}.
diff --git a/lib/reltool/test/reltool_server_SUITE_data/otp_9229/x-1.0/src/mylib.erl b/lib/reltool/test/reltool_server_SUITE_data/otp_9229/x-1.0/src/mylib.erl
new file mode 100644
index 0000000000..c8603d1a8e
--- /dev/null
+++ b/lib/reltool/test/reltool_server_SUITE_data/otp_9229/x-1.0/src/mylib.erl
@@ -0,0 +1,4 @@
+-module(mylib).
+-export([foo/0]).
+
+foo() -> erlang:time().
diff --git a/lib/reltool/test/reltool_server_SUITE_data/otp_9229/x-1.0/src/x.erl b/lib/reltool/test/reltool_server_SUITE_data/otp_9229/x-1.0/src/x.erl
new file mode 100644
index 0000000000..17ff84f08f
--- /dev/null
+++ b/lib/reltool/test/reltool_server_SUITE_data/otp_9229/x-1.0/src/x.erl
@@ -0,0 +1,4 @@
+-module(x).
+-export([x/0]).
+
+x() ->mylib:foo().
diff --git a/lib/reltool/test/reltool_server_SUITE_data/otp_9229/y-1.0/ebin/y.app b/lib/reltool/test/reltool_server_SUITE_data/otp_9229/y-1.0/ebin/y.app
new file mode 100644
index 0000000000..5b327862e3
--- /dev/null
+++ b/lib/reltool/test/reltool_server_SUITE_data/otp_9229/y-1.0/ebin/y.app
@@ -0,0 +1,7 @@
+%% -*- erlang -*-
+{application, y,
+ [{description, "Y CXC 138 11"},
+ {vsn, "1.0"},
+ {modules, [y, mylib]},
+ {registered, []},
+ {applications, [kernel, stdlib]}]}.
diff --git a/lib/reltool/test/reltool_server_SUITE_data/otp_9229/y-1.0/src/mylib.erl b/lib/reltool/test/reltool_server_SUITE_data/otp_9229/y-1.0/src/mylib.erl
new file mode 100644
index 0000000000..c8603d1a8e
--- /dev/null
+++ b/lib/reltool/test/reltool_server_SUITE_data/otp_9229/y-1.0/src/mylib.erl
@@ -0,0 +1,4 @@
+-module(mylib).
+-export([foo/0]).
+
+foo() -> erlang:time().
diff --git a/lib/reltool/test/reltool_server_SUITE_data/otp_9229/y-1.0/src/y.erl b/lib/reltool/test/reltool_server_SUITE_data/otp_9229/y-1.0/src/y.erl
new file mode 100644
index 0000000000..342e7da7d5
--- /dev/null
+++ b/lib/reltool/test/reltool_server_SUITE_data/otp_9229/y-1.0/src/y.erl
@@ -0,0 +1,4 @@
+-module(y).
+-export([y/0]).
+
+y() ->mylib:foo().
diff --git a/lib/sasl/Makefile b/lib/sasl/Makefile
index 2affcf1e40..4073e5af85 100644
--- a/lib/sasl/Makefile
+++ b/lib/sasl/Makefile
@@ -1,7 +1,7 @@
#
# %CopyrightBegin%
#
-# Copyright Ericsson AB 1996-2009. All Rights Reserved.
+# Copyright Ericsson AB 1996-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
@@ -23,7 +23,7 @@ include $(ERL_TOP)/make/$(TARGET)/otp.mk
# Macros
#
-SUB_DIRECTORIES = src doc/src
+SUB_DIRECTORIES = src doc/src examples/src
include vsn.mk
VSN = $(SASL_VSN)
diff --git a/lib/sasl/examples/ebin/.gitignore b/lib/sasl/examples/ebin/.gitignore
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/lib/sasl/examples/ebin/.gitignore
diff --git a/lib/sasl/examples/src/Makefile b/lib/sasl/examples/src/Makefile
new file mode 100644
index 0000000000..4a4e04a536
--- /dev/null
+++ b/lib/sasl/examples/src/Makefile
@@ -0,0 +1,78 @@
+#
+# %CopyrightBegin%
+#
+# Copyright Ericsson AB 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%
+#
+
+#
+
+include $(ERL_TOP)/make/target.mk
+include $(ERL_TOP)/make/$(TARGET)/otp.mk
+
+# ----------------------------------------------------
+# Application version
+# ----------------------------------------------------
+include ../../vsn.mk
+VSN=$(SASL_VSN)
+
+# ----------------------------------------------------
+# Release directory specification
+# ----------------------------------------------------
+RELSYSDIR = $(RELEASE_PATH)/lib/sasl-$(VSN)
+
+# ----------------------------------------------------
+# Common Macros
+# ----------------------------------------------------
+EXTRA_ERLC_FLAGS = +warn_unused_vars
+ERL_COMPILE_FLAGS += $(EXTRA_ERLC_FLAGS)
+
+
+MODULES = target_system
+
+ERL_FILES= $(MODULES:%=%.erl)
+
+TARGET_FILES= $(MODULES:%=$(EBIN)/%.$(EMULATOR))
+
+# ----------------------------------------------------
+# Targets
+# ----------------------------------------------------
+
+debug opt: $(TARGET_FILES)
+
+clean:
+ rm -fr $(TARGET_FILES) *~ *.beam
+
+docs:
+
+# ----------------------------------------------------
+# Release Target
+# ----------------------------------------------------
+include $(ERL_TOP)/make/otp_release_targets.mk
+
+release_spec: opt
+ $(INSTALL_DIR) $(RELSYSDIR)/examples/src
+ $(INSTALL_DIR) $(RELSYSDIR)/examples/ebin
+ (cd ..; tar cf - src ebin | (cd $(RELSYSDIR)/examples; tar xf -))
+ chmod -f -R ug+w $(RELSYSDIR)/examples
+
+release_docs_spec:
+
+
+
+
+
+
+
diff --git a/lib/sasl/examples/src/target_system.erl b/lib/sasl/examples/src/target_system.erl
new file mode 100644
index 0000000000..0e1e0b2324
--- /dev/null
+++ b/lib/sasl/examples/src/target_system.erl
@@ -0,0 +1,259 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2011. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(target_system).
+-export([create/1, create/2, install/2]).
+
+%% Note: RelFileName below is the *stem* without trailing .rel,
+%% .script etc.
+%%
+
+%% create(RelFileName)
+%%
+create(RelFileName) ->
+ create(RelFileName,[]).
+
+create(RelFileName,SystoolsOpts) ->
+ RelFile = RelFileName ++ ".rel",
+ Dir = filename:dirname(RelFileName),
+ PlainRelFileName = filename:join(Dir,"plain"),
+ PlainRelFile = PlainRelFileName ++ ".rel",
+ io:fwrite("Reading file: ~p ...~n", [RelFile]),
+ {ok, [RelSpec]} = file:consult(RelFile),
+ io:fwrite("Creating file: ~p from ~p ...~n",
+ [PlainRelFile, RelFile]),
+ {release,
+ {RelName, RelVsn},
+ {erts, ErtsVsn},
+ AppVsns} = RelSpec,
+ PlainRelSpec = {release,
+ {RelName, RelVsn},
+ {erts, ErtsVsn},
+ lists:filter(fun({kernel, _}) ->
+ true;
+ ({stdlib, _}) ->
+ true;
+ (_) ->
+ false
+ end, AppVsns)
+ },
+ {ok, Fd} = file:open(PlainRelFile, [write]),
+ io:fwrite(Fd, "~p.~n", [PlainRelSpec]),
+ file:close(Fd),
+
+ io:fwrite("Making \"~s.script\" and \"~s.boot\" files ...~n",
+ [PlainRelFileName,PlainRelFileName]),
+ make_script(PlainRelFileName,SystoolsOpts),
+
+ io:fwrite("Making \"~s.script\" and \"~s.boot\" files ...~n",
+ [RelFileName, RelFileName]),
+ make_script(RelFileName,SystoolsOpts),
+
+ TarFileName = filename:join(Dir,RelFileName ++ ".tar.gz"),
+ io:fwrite("Creating tar file ~p ...~n", [TarFileName]),
+ make_tar(RelFileName,SystoolsOpts),
+
+ TmpDir = filename:join(Dir,"tmp"),
+ io:fwrite("Creating directory ~p ...~n",[TmpDir]),
+ file:make_dir(TmpDir),
+
+ io:fwrite("Extracting ~p into directory ~p ...~n", [TarFileName,TmpDir]),
+ extract_tar(TarFileName, TmpDir),
+
+ TmpBinDir = filename:join([TmpDir, "bin"]),
+ ErtsBinDir = filename:join([TmpDir, "erts-" ++ ErtsVsn, "bin"]),
+ io:fwrite("Deleting \"erl\" and \"start\" in directory ~p ...~n",
+ [ErtsBinDir]),
+ file:delete(filename:join([ErtsBinDir, "erl"])),
+ file:delete(filename:join([ErtsBinDir, "start"])),
+
+ io:fwrite("Creating temporary directory ~p ...~n", [TmpBinDir]),
+ file:make_dir(TmpBinDir),
+
+ io:fwrite("Copying file \"~s.boot\" to ~p ...~n",
+ [PlainRelFileName, filename:join([TmpBinDir, "start.boot"])]),
+ copy_file(PlainRelFileName++".boot",filename:join([TmpBinDir, "start.boot"])),
+
+ io:fwrite("Copying files \"epmd\", \"run_erl\" and \"to_erl\" from \n"
+ "~p to ~p ...~n",
+ [ErtsBinDir, TmpBinDir]),
+ copy_file(filename:join([ErtsBinDir, "epmd"]),
+ filename:join([TmpBinDir, "epmd"]), [preserve]),
+ copy_file(filename:join([ErtsBinDir, "run_erl"]),
+ filename:join([TmpBinDir, "run_erl"]), [preserve]),
+ copy_file(filename:join([ErtsBinDir, "to_erl"]),
+ filename:join([TmpBinDir, "to_erl"]), [preserve]),
+
+ StartErlDataFile = filename:join([TmpDir, "releases", "start_erl.data"]),
+ io:fwrite("Creating ~p ...~n", [StartErlDataFile]),
+ StartErlData = io_lib:fwrite("~s ~s~n", [ErtsVsn, RelVsn]),
+ write_file(StartErlDataFile, StartErlData),
+
+ io:fwrite("Recreating tar file ~p from contents in directory ~p ...~n",
+ [TarFileName,TmpDir]),
+ {ok, Tar} = erl_tar:open(TarFileName, [write, compressed]),
+ %% {ok, Cwd} = file:get_cwd(),
+ %% file:set_cwd("tmp"),
+ ErtsDir = "erts-"++ErtsVsn,
+ erl_tar:add(Tar, filename:join(TmpDir,"bin"), "bin", []),
+ erl_tar:add(Tar, filename:join(TmpDir,ErtsDir), ErtsDir, []),
+ erl_tar:add(Tar, filename:join(TmpDir,"releases"), "releases", []),
+ erl_tar:add(Tar, filename:join(TmpDir,"lib"), "lib", []),
+ erl_tar:close(Tar),
+ %% file:set_cwd(Cwd),
+ io:fwrite("Removing directory ~p ...~n",[TmpDir]),
+ remove_dir_tree(TmpDir),
+ ok.
+
+
+install(RelFileName, RootDir) ->
+ TarFile = RelFileName ++ ".tar.gz",
+ io:fwrite("Extracting ~p ...~n", [TarFile]),
+ extract_tar(TarFile, RootDir),
+ StartErlDataFile = filename:join([RootDir, "releases", "start_erl.data"]),
+ {ok, StartErlData} = read_txt_file(StartErlDataFile),
+ [ErlVsn, _RelVsn| _] = string:tokens(StartErlData, " \n"),
+ ErtsBinDir = filename:join([RootDir, "erts-" ++ ErlVsn, "bin"]),
+ BinDir = filename:join([RootDir, "bin"]),
+ io:fwrite("Substituting in erl.src, start.src and start_erl.src to\n"
+ "form erl, start and start_erl ...\n"),
+ subst_src_scripts(["erl", "start", "start_erl"], ErtsBinDir, BinDir,
+ [{"FINAL_ROOTDIR", RootDir}, {"EMU", "beam"}],
+ [preserve]),
+ io:fwrite("Creating the RELEASES file ...\n"),
+ create_RELEASES(RootDir,
+ filename:join([RootDir, "releases", RelFileName])).
+
+%% LOCALS
+
+%% make_script(RelFileName,Opts)
+%%
+make_script(RelFileName,Opts) ->
+ systools:make_script(RelFileName, [no_module_tests,
+ {outdir,filename:dirname(RelFileName)}
+ |Opts]).
+
+%% make_tar(RelFileName,Opts)
+%%
+make_tar(RelFileName,Opts) ->
+ RootDir = code:root_dir(),
+ systools:make_tar(RelFileName, [{erts, RootDir},
+ {outdir,filename:dirname(RelFileName)}
+ |Opts]).
+
+%% extract_tar(TarFile, DestDir)
+%%
+extract_tar(TarFile, DestDir) ->
+ erl_tar:extract(TarFile, [{cwd, DestDir}, compressed]).
+
+create_RELEASES(DestDir, RelFileName) ->
+ release_handler:create_RELEASES(DestDir, RelFileName ++ ".rel").
+
+subst_src_scripts(Scripts, SrcDir, DestDir, Vars, Opts) ->
+ lists:foreach(fun(Script) ->
+ subst_src_script(Script, SrcDir, DestDir,
+ Vars, Opts)
+ end, Scripts).
+
+subst_src_script(Script, SrcDir, DestDir, Vars, Opts) ->
+ subst_file(filename:join([SrcDir, Script ++ ".src"]),
+ filename:join([DestDir, Script]),
+ Vars, Opts).
+
+subst_file(Src, Dest, Vars, Opts) ->
+ {ok, Conts} = read_txt_file(Src),
+ NConts = subst(Conts, Vars),
+ write_file(Dest, NConts),
+ case lists:member(preserve, Opts) of
+ true ->
+ {ok, FileInfo} = file:read_file_info(Src),
+ file:write_file_info(Dest, FileInfo);
+ false ->
+ ok
+ end.
+
+%% subst(Str, Vars)
+%% Vars = [{Var, Val}]
+%% Var = Val = string()
+%% Substitute all occurrences of %Var% for Val in Str, using the list
+%% of variables in Vars.
+%%
+subst(Str, Vars) ->
+ subst(Str, Vars, []).
+
+subst([$%, C| Rest], Vars, Result) when $A =< C, C =< $Z ->
+ subst_var([C| Rest], Vars, Result, []);
+subst([$%, C| Rest], Vars, Result) when $a =< C, C =< $z ->
+ subst_var([C| Rest], Vars, Result, []);
+subst([$%, C| Rest], Vars, Result) when C == $_ ->
+ subst_var([C| Rest], Vars, Result, []);
+subst([C| Rest], Vars, Result) ->
+ subst(Rest, Vars, [C| Result]);
+subst([], _Vars, Result) ->
+ lists:reverse(Result).
+
+subst_var([$%| Rest], Vars, Result, VarAcc) ->
+ Key = lists:reverse(VarAcc),
+ case lists:keysearch(Key, 1, Vars) of
+ {value, {Key, Value}} ->
+ subst(Rest, Vars, lists:reverse(Value, Result));
+ false ->
+ subst(Rest, Vars, [$%| VarAcc ++ [$%| Result]])
+ end;
+subst_var([C| Rest], Vars, Result, VarAcc) ->
+ subst_var(Rest, Vars, Result, [C| VarAcc]);
+subst_var([], Vars, Result, VarAcc) ->
+ subst([], Vars, [VarAcc ++ [$%| Result]]).
+
+copy_file(Src, Dest) ->
+ copy_file(Src, Dest, []).
+
+copy_file(Src, Dest, Opts) ->
+ {ok,_} = file:copy(Src, Dest),
+ case lists:member(preserve, Opts) of
+ true ->
+ {ok, FileInfo} = file:read_file_info(Src),
+ file:write_file_info(Dest, FileInfo);
+ false ->
+ ok
+ end.
+
+write_file(FName, Conts) ->
+ {ok, Fd} = file:open(FName, [write]),
+ file:write(Fd, Conts),
+ file:close(Fd).
+
+read_txt_file(File) ->
+ {ok, Bin} = file:read_file(File),
+ {ok, binary_to_list(Bin)}.
+
+remove_dir_tree(Dir) ->
+ remove_all_files(".", [Dir]).
+
+remove_all_files(Dir, Files) ->
+ lists:foreach(fun(File) ->
+ FilePath = filename:join([Dir, File]),
+ case filelib:is_dir(FilePath) of
+ true ->
+ {ok, DirFiles} = file:list_dir(FilePath),
+ remove_all_files(FilePath, DirFiles),
+ file:del_dir(FilePath);
+ _ ->
+ file:delete(FilePath)
+ end
+ end, Files).
diff --git a/lib/sasl/src/rb.erl b/lib/sasl/src/rb.erl
index 13753565d8..8004ef2c5a 100644
--- a/lib/sasl/src/rb.erl
+++ b/lib/sasl/src/rb.erl
@@ -53,8 +53,7 @@ start_link(Options) ->
gen_server:start_link({local, rb_server}, rb, Options, []).
stop() ->
- call(stop),
- supervisor:delete_child(sasl_sup, rb_server).
+ supervisor:terminate_child(sasl_sup, rb_server).
rescan() -> rescan([]).
rescan(Options) ->
@@ -205,8 +204,6 @@ handle_call({rescan, Options}, _From, State) ->
NewState = State#state{data = Data, max = Max, type = Type,
device = Device, abort = Abort, log = Log1},
{reply, ok, NewState};
-handle_call(stop, _From, State) ->
- {stop, normal, stopped, State};
handle_call(_, _From, #state{data = undefined}) ->
{reply, {error, no_data}, #state{}};
handle_call({list, Type}, _From, State) ->
@@ -312,11 +309,14 @@ scan_files(RptDir, Max, Type) ->
{ok, Fd} ->
case catch file:read(Fd, 1) of
{ok, [LastWritten]} ->
+ file:close(Fd),
Files = make_file_list(RptDir, LastWritten),
scan_files(RptDir, Files, Max, Type);
- _ -> exit("cannot read the index file")
+ _X ->
+ file:close(Fd),
+ exit("cannot read the index file")
end;
- _ -> exit("cannot read the index file")
+ _X -> exit("cannot read the index file")
end.
make_file_list(Dir, FirstFileNo) ->
@@ -789,7 +789,7 @@ filter_report([{Key, RegExp, re}|T], Msg) ->
filter_report([{Key, RegExp, re, no}|T], Msg) ->
case proplists:get_value(Key, Msg) of
undefined ->
- false;
+ true;
Value ->
Subject = lists:flatten(io_lib:format("~p",[Value])),
case run_re(Subject, RegExp) of
diff --git a/lib/sasl/test/.gitignore b/lib/sasl/test/.gitignore
new file mode 100644
index 0000000000..76e706b874
--- /dev/null
+++ b/lib/sasl/test/.gitignore
@@ -0,0 +1,5 @@
+#
+# Don't ignore *.beam files in any sub-directory.
+#
+
+!*.beam
diff --git a/lib/sasl/test/Makefile b/lib/sasl/test/Makefile
new file mode 100644
index 0000000000..ad08c8136b
--- /dev/null
+++ b/lib/sasl/test/Makefile
@@ -0,0 +1,91 @@
+#
+# %CopyrightBegin%
+#
+# Copyright Ericsson AB 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%
+#
+include $(ERL_TOP)/make/target.mk
+include $(ERL_TOP)/make/$(TARGET)/otp.mk
+
+# ----------------------------------------------------
+# Target Specs
+# ----------------------------------------------------
+
+MODULES= \
+ sasl_SUITE \
+ alarm_handler_SUITE \
+ installer \
+ release_handler_SUITE \
+ systools_SUITE \
+ systools_rc_SUITE \
+ overload_SUITE \
+ rb_SUITE
+
+ERL_FILES= $(MODULES:%=%.erl)
+
+TARGET_FILES= $(MODULES:%=$(EBIN)/%.$(EMULATOR))
+INSTALL_PROGS= $(TARGET_FILES)
+
+EMAKEFILE=Emakefile
+
+# ----------------------------------------------------
+# Release directory specification
+# ----------------------------------------------------
+RELSYSDIR = $(RELEASE_PATH)/sasl_test
+
+# ----------------------------------------------------
+# FLAGS
+# ----------------------------------------------------
+ERL_MAKE_FLAGS +=
+ERL_COMPILE_FLAGS += -I$(ERL_TOP)/lib/test_server/include \
+ -I$(ERL_TOP)/lib/sasl/src
+
+EBIN = .
+
+# ----------------------------------------------------
+# Targets
+# ----------------------------------------------------
+
+make_emakefile:
+ $(ERL_TOP)/make/make_emakefile $(ERL_COMPILE_FLAGS) -o$(EBIN) '*_SUITE_make'\
+ > $(EMAKEFILE)
+ $(ERL_TOP)/make/make_emakefile $(ERL_COMPILE_FLAGS) -o$(EBIN) $(MODULES)\
+ >> $(EMAKEFILE)
+
+tests debug opt: make_emakefile
+ erl $(ERL_MAKE_FLAGS) -make
+
+clean:
+ rm -f $(EMAKEFILE)
+ rm -f $(TARGET_FILES)
+ rm -f core
+
+docs:
+
+# ----------------------------------------------------
+# Release Target
+# ----------------------------------------------------
+include $(ERL_TOP)/make/otp_release_targets.mk
+
+release_spec: opt
+
+release_tests_spec: make_emakefile
+ $(INSTALL_DIR) $(RELSYSDIR)
+ $(INSTALL_DATA) $(ERL_FILES) $(RELSYSDIR)
+ $(INSTALL_DATA) sasl.spec sasl.cover $(EMAKEFILE) $(RELSYSDIR)
+ chmod -f -R u+w $(RELSYSDIR)
+ @tar cfh - *_SUITE_data | (cd $(RELSYSDIR); tar xf -)
+
+release_docs_spec:
diff --git a/lib/sasl/test/alarm_handler_SUITE.erl b/lib/sasl/test/alarm_handler_SUITE.erl
new file mode 100644
index 0000000000..a98e8c9c67
--- /dev/null
+++ b/lib/sasl/test/alarm_handler_SUITE.erl
@@ -0,0 +1,179 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2011. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(alarm_handler_SUITE).
+
+-include_lib("test_server/include/test_server.hrl").
+
+%%-----------------------------------------------------------------
+%% We will add an own alarm handler in order to verify that the
+%% alarm_handler deliver the expected events.
+%%-----------------------------------------------------------------
+
+-export([init_per_suite/1, end_per_suite/1, all/0,groups/0,
+ init_per_group/2,end_per_group/2,
+ set_alarm/1, clear_alarm/1, swap/1]).
+
+-export([init/1, handle_event/2, handle_call/2, handle_info/2,
+ terminate/2]).
+
+
+init_per_suite(Config) ->
+ application:start(sasl),
+ Config.
+
+end_per_suite(_Config) ->
+ ok.
+
+all() ->
+ [set_alarm, clear_alarm, swap].
+
+groups() ->
+ [].
+
+init_per_group(_GroupName, Config) ->
+ Config.
+
+end_per_group(_GroupName, Config) ->
+ Config.
+
+
+
+%%-----------------------------------------------------------------
+
+set_alarm(suite) -> [];
+set_alarm(Config) when is_list(Config) ->
+ ?line gen_event:add_handler(alarm_handler, ?MODULE, self()),
+ Alarm1 = {alarm1, "this is the alarm"},
+ Alarm2 = {"alarm2", this_is_the_alarm},
+ Alarm3 = {{alarm3}, {this_is,"the_alarm"}},
+ ?line ok = alarm_handler:set_alarm(Alarm1),
+ reported(set_alarm, Alarm1),
+ ?line ok = alarm_handler:set_alarm(Alarm2),
+ reported(set_alarm, Alarm2),
+ ?line ok = alarm_handler:set_alarm(Alarm3),
+ reported(set_alarm, Alarm3),
+
+ ?line [Alarm3,Alarm2,Alarm1] = alarm_handler:get_alarms(),
+ alarm_handler:clear_alarm(alarm1),
+ alarm_handler:clear_alarm("alarm2"),
+ alarm_handler:clear_alarm({alarm3}),
+ ?line [] = alarm_handler:get_alarms(),
+
+ test_server:messages_get(),
+ ?line my_yes = gen_event:delete_handler(alarm_handler, ?MODULE, []),
+ ok.
+
+%%-----------------------------------------------------------------
+
+clear_alarm(suite) -> [];
+clear_alarm(Config) when is_list(Config) ->
+ ?line gen_event:add_handler(alarm_handler, ?MODULE, self()),
+ Alarm1 = {alarm1, "this is the alarm"},
+ Alarm2 = {"alarm2", this_is_the_alarm},
+ Alarm3 = {{alarm3}, {this_is,"the_alarm"}},
+ alarm_handler:set_alarm(Alarm1),
+ alarm_handler:set_alarm(Alarm2),
+ alarm_handler:set_alarm(Alarm3),
+ test_server:messages_get(),
+
+ ?line ok = alarm_handler:clear_alarm(alarm1),
+ reported(clear_alarm, alarm1),
+ ?line ok = alarm_handler:clear_alarm("alarm2"),
+ reported(clear_alarm, "alarm2"),
+ ?line ok = alarm_handler:clear_alarm({alarm3}),
+ reported(clear_alarm, {alarm3}),
+ ?line [] = alarm_handler:get_alarms(),
+
+ ?line my_yes = gen_event:delete_handler(alarm_handler, ?MODULE, []),
+ ok.
+
+%%-----------------------------------------------------------------
+
+swap(suite) -> [];
+swap(Config) when is_list(Config) ->
+ ?line Alarm1 = {alarm1, "this is the alarm"},
+ ?line Alarm2 = {"alarm2", this_is_the_alarm},
+ ?line Alarm3 = {{alarm3}, {this_is,"the_alarm"}},
+ ?line alarm_handler:set_alarm(Alarm1),
+ ?line alarm_handler:set_alarm(Alarm2),
+ ?line alarm_handler:set_alarm(Alarm3),
+
+ ?line foo,
+ case gen_event:which_handlers(alarm_handler) of
+ [alarm_handler] ->
+ ?line ok = gen_event:swap_handler(alarm_handler,
+ {alarm_handler, swap},
+ {?MODULE, self()}),
+ ?line [?MODULE] = gen_event:which_handlers(alarm_handler),
+ Alarms = [Alarm3, Alarm2, Alarm1],
+ reported(swap_alarms, Alarms),
+
+ %% get_alarms is only valid with the default handler installed.
+ ?line {error, _} = alarm_handler:get_alarms(),
+
+ ?line my_yes = gen_event:delete_handler(alarm_handler,
+ ?MODULE, []),
+ ?line gen_event:add_handler(alarm_handler, alarm_handler, []),
+ ok;
+ _ ->
+ alarm_handler:clear_alarm(alarm1),
+ alarm_handler:clear_alarm("alarm2"),
+ alarm_handler:clear_alarm({alarm3}),
+ ok
+ end.
+
+%%-----------------------------------------------------------------
+%% Check that the alarm has been received.
+%%-----------------------------------------------------------------
+reported(Tag, Data) ->
+ receive
+ {Tag, Data} ->
+ test_server:messages_get(),
+ ok
+ after 1000 ->
+ test_server:fail(no_alarm_received)
+ end.
+
+%%-----------------------------------------------------------------
+%% The error_logger handler (gen_event behaviour).
+%% Sends a notification to the Tester process about the events
+%% generated by the Tester process.
+%%-----------------------------------------------------------------
+init(Tester) when is_pid(Tester) ->
+ {ok, Tester};
+init({Tester, {alarm_handler,Alarms}}) -> % Swap from default handler.
+ Tester ! {swap_alarms, Alarms},
+ {ok, Tester}.
+
+handle_event({set_alarm, Alarm}, Tester) ->
+ Tester ! {set_alarm, Alarm},
+ {ok, Tester};
+handle_event({clear_alarm, AlarmId}, Tester) ->
+ Tester ! {clear_alarm, AlarmId},
+ {ok, Tester};
+handle_event(_Event, Tester) ->
+ {ok, Tester}.
+
+handle_info(_, Tester) ->
+ {ok, Tester}.
+
+handle_call(_Query, Tester) -> {ok, {error, bad_query}, Tester}.
+
+terminate(_Reason, _Tester) ->
+ my_yes.
diff --git a/lib/sasl/test/installer.erl b/lib/sasl/test/installer.erl
new file mode 100644
index 0000000000..a114c4b5c9
--- /dev/null
+++ b/lib/sasl/test/installer.erl
@@ -0,0 +1,778 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2011. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+-module(installer).
+
+%%-compile(export_all).
+-export([install_1/2]).
+-export([install_2/1]).
+-export([install_3/2]).
+-export([install_3a/1]).
+-export([install_4/1]).
+-export([install_5/1]).
+-export([install_5a/1]).
+-export([install_6/1]).
+-export([install_7/1]).
+-export([install_8/1]).
+-export([install_9/1]).
+-export([install_10/1]).
+-export([install_11/1]).
+-export([client1_1/4]).
+-export([client2/3]).
+-export([stop/1]).
+-export([unpack_p1h/2]).
+-export([permanent_p1h/1]).
+-export([reg_proc/1]).
+-export([registered_loop/1]).
+
+-define(print(List), {rh_print, TestNode} ! {print, {?MODULE, ?LINE}, List}).
+-define(print_line(Line,List), {rh_print, TestNode} ! {print, {?MODULE, Line}, List}).
+-define(fail(Term), exit({?MODULE, ?LINE, Term})).
+-define(fail_line(Line,Term), exit({?MODULE, Line, Term})).
+
+-define(check_release(Vsn,Status,Apps),
+ check_release(TestNode,node(),Vsn,Status,Apps,?LINE)).
+-define(check_release_client(Node,Vsn,Status,Apps),
+ check_release(TestNode,Node,Vsn,Status,Apps,?LINE)).
+
+-define(check_running_app(App,Vsn),
+ check_running_app(TestNode,node(),App,Vsn,?LINE)).
+-define(check_running_app_client(Node,App,Vsn),
+ check_running_app(TestNode,Node,App,Vsn,?LINE)).
+
+
+install_1(TestNode,PrivDir) ->
+ ?print([TestNode]),
+ ?print(["install_1 start"]),
+
+ % Unpack and install P1H
+ {ok, "P1H"} = unpack_release(PrivDir,"rel1"),
+ ?print(["unpack_release P1H ok"]),
+ ?check_release("P1H",unpacked,["a-1.0"]),
+ {ok,"P1G",[new_appl]} = release_handler:install_release("P1H"),
+ ?print(["install_release P1H ok"]),
+ ?check_release("P1H",current,["a-1.0"]),
+ ?check_running_app(a,"1.0"),
+ X = a:a(),
+ ?print(["X", X]),
+ {key2, val2} = lists:keyfind(key2, 1, X),
+ {key1, val1} = lists:keyfind(key1, 1, X),
+ ?print(["install_1 end OK"]),
+ ok.
+ % release_handler_SUITE will reboot this node now!
+
+install_2(TestNode) ->
+ ?print(["install_2 start"]),
+
+ % Check that P1H is still unpacked, install it and make_permanent
+ ?check_release("P1H",unpacked,["a-1.0"]),
+ ?print(["install_2 P1H unpacked"]),
+ {ok,"P1G",[new_appl]} = release_handler:install_release("P1H"),
+ ?print(["install_2 install_release ok"]),
+ ?check_release("P1H",current,["a-1.0"]),
+ ?check_running_app(a,"1.0"),
+ ok = release_handler:make_permanent("P1H").
+ % release_handler_SUITE will reboot this node now!
+
+install_3(TestNode,PrivDir) ->
+ ?print(["install_3 start"]),
+
+ % Check that P1H is permanent
+ ?check_release("P1H",permanent,["a-1.0"]),
+ X = a:a(),
+ {key2, val2} = lists:keyfind(key2, 1, X),
+ {key1, val1} = lists:keyfind(key1, 1, X),
+
+ % Unpack and install P1I
+ {ok, "P1I"} = unpack_release(PrivDir,"rel2"),
+ ?print(["install_3 unpack_release P1I ok"]),
+ ?check_release("P1I",unpacked,["a-1.1"]),
+ {ok,"P1H",[{extra, gott}]} = release_handler:check_install_release("P1I"),
+ {error,_} = release_handler:check_install_release("P1J"),
+ {ok,"P1H",[{extra, gott}]} = release_handler:install_release("P1I"),
+ ?print(["install_3 install_release P1I ok"]),
+ ?check_release("P1I",current,["a-1.1"]),
+ ?check_running_app(a,"1.1"),
+ X2 = a:a(),
+ {key2, newval2} = lists:keyfind(key2, 1, X2),
+ {key1, val1} = lists:keyfind(key1, 1, X2),
+ {ok, bval} = a:b(),
+
+ % Unpack and install P2A
+ {ok, "P2A"} = unpack_release(PrivDir,"rel3"),
+ ?print(["install_3 unpack_release P2A ok"]),
+ ?check_release("P2A",unpacked,["a-1.1"]),
+ {ok, "P1I", [new_emu]} = release_handler:check_install_release("P2A"),
+ ok = release_handler:make_permanent("P1I"),
+ ?print(["install_3 make_permanent P1I ok"]),
+ ?check_release("P1I",permanent,["a-1.1"]),
+ ok.
+
+install_3a(TestNode) ->
+ {ok, "P1I", [new_emu]} = release_handler:install_release("P2A"),
+ %% Node is rebooted by the release_handler:install_release
+ %% (init:reboot) because P2A includes a new erts vsn and the relup
+ %% file contains a 'restart_new_emulator' instruction.
+ ?print(["install_3 P2A installed"]),
+ ok.
+
+
+
+install_4(TestNode) ->
+ ?print(["install_4 start"]),
+
+ % Check that P2A is in use.
+ ?check_release("P2A",current,["a-1.1"]),
+ ?check_running_app(a,"1.1"),
+ X = a:a(),
+ {key2, newval2} = lists:keyfind(key2, 1, X),
+ {key1, val1} = lists:keyfind(key1, 1, X),
+ {ok, bval} = a:b(),
+ ok.
+ % release_handler_SUITE will reboot this node now!
+
+install_5(TestNode) ->
+ ?print(["install_5 start"]),
+
+ % Check that P1I is used
+ {ok, "P1I", [new_emu]} = release_handler:check_install_release("P2A"),
+ ok.
+
+install_5a(TestNode) ->
+ % Install P2A again
+ {ok, "P1I", [new_emu]} = release_handler:install_release("P2A"),
+ %% Node is rebooted by the release_handler:install_release
+ %% (init:reboot) because P2A includes a new erts vsn and the relup
+ %% file contains a 'restart_new_emulator' instruction.
+ ?print(["install_5 P2A installed"]),
+ ok.
+
+install_6(TestNode) ->
+ ?print(["install_6 start"]),
+
+ % Check that P2A is used
+ ?check_release("P2A",current,["a-1.1"]),
+ ?check_running_app(a,"1.1"),
+ X = a:a(),
+ {key2, newval2} = lists:keyfind(key2, 1, X),
+ {key1, val1} = lists:keyfind(key1, 1, X),
+ {ok, bval} = a:b(),
+ ok = release_handler:make_permanent("P2A").
+ % release_handler_SUITE will reboot this node now!
+
+
+install_7(TestNode) ->
+ ?print(["install_7 start"]),
+
+ % Check that P2A is used
+ ?check_release("P2A",permanent,["a-1.1"]),
+
+ % Install old P1H
+ ok = release_handler:reboot_old_release("P1H"),
+ ok.
+
+install_8(TestNode) ->
+ ?print(["install_8 start"]),
+
+ % Check that P1H is permanent
+ ?check_release("P1H",permanent,["a-1.0"]),
+ X = a:a(),
+ {key2, val2} = lists:keyfind(key2, 1, X),
+ {key1, val1} = lists:keyfind(key1, 1, X),
+
+ %% Remove P1I and P2A and check that a-1.1 and erts-<latest> are removed
+ ok = release_handler:remove_release("P2A"),
+ ok = release_handler:remove_release("P1I"),
+ {ok, Libs} = file:list_dir(code:lib_dir()),
+ {_,_,StdlibVsn} = lists:keyfind(stdlib,1,application:which_applications()),
+ true = lists:member("stdlib-"++StdlibVsn, Libs),
+ true = lists:member("a-1.0", Libs),
+ false = lists:member("a-1.1", Libs),
+ {ok, Dirs} = file:list_dir(code:root_dir()),
+ ["erts-4.4"] = lists:filter(fun(Dir) -> lists:prefix("erts-",Dir) end, Dirs),
+ ok.
+ % release_handler_SUITE will reboot this node now!
+
+install_9(TestNode) ->
+ ?print(["install_9 start"]),
+
+ % Check that P1H is permanent
+ ?check_release("P1H",permanent,["a-1.0"]),
+ X = a:a(),
+ {key2, val2} = lists:keyfind(key2, 1, X),
+ {key1, val1} = lists:keyfind(key1, 1, X),
+
+ % Install old P1G
+ ok = release_handler:reboot_old_release("P1G"),
+ ok.
+
+install_10(TestNode) ->
+ ?print(["install_10 start"]),
+
+ % Check that P1G is permanent
+ ?check_release("P1G",permanent,[]),
+ ?check_release("P1H",old,["a-1.0"]),
+
+ %% Remove P1H and check that both versions of application a is removed
+ ok = release_handler:remove_release("P1H"),
+ {ok, Libs} = file:list_dir(code:lib_dir()),
+ {_,_,StdlibVsn} = lists:keyfind(stdlib,1,application:which_applications()),
+ true = lists:member("stdlib-"++StdlibVsn, Libs),
+ false = lists:member("a-1.0", Libs),
+ false = lists:member("a-1.1", Libs),
+ ok.
+ % release_handler_SUITE will reboot this node now!
+
+install_11(TestNode) ->
+ ?print(["install_11 start"]),
+
+ % Check that P1G is permanent
+ ?check_release("P1G",permanent,[]),
+ ok.
+
+
+
+%%-----------------------------------------------------------------
+%% This test starts a client node which uses this node as master
+%% for the release_handler.
+%% The client node runs all tests as in installer/1 test case.
+%% Thus, the client node will be rebooted several times.
+%% The to_erl /tmp/NODENAME@HOSTNAME/ command can be used to connect
+%% to the client node.
+%% run_erl logs for the client can be found in the directory:
+%% code:root_dir() ++ "/clients/type1/NODENAME@HOSTNAME/log
+%%-----------------------------------------------------------------
+
+
+client1_1(TestNode,PrivDir,MasterDir,ClientSname) ->
+ TestHost = test_host(),
+ ?print(["client1_1 start"]),
+
+ {ok,IP} = inet:getaddr(TestHost,inet),
+ erl_boot_server:start([IP]),
+
+ ok = net_kernel:monitor_nodes(true),
+ Node = start_client(TestNode,ClientSname),
+ trace_disallowed_calls(Node),
+
+ %% Check env var for SASL on client node
+ SaslEnv = rpc:call(Node, application, get_all_env, [sasl]),
+ {_,CliDir} = lists:keyfind(client_directory,1,SaslEnv),
+ {_,[Master]} = lists:keyfind(masters,1,SaslEnv),
+ {_,StartCli} = lists:keyfind(start_prg,1,SaslEnv),
+ Root = code:root_dir(),
+ true = (CliDir =:= filename:join([Root,"clients","type1",Node])),
+ true = (StartCli =:= filename:join([CliDir,"bin","start"])),
+ true = (Master =:= node()),
+
+ %% Unpack P1H on master
+ {ok, "P1H"} = unpack_release(PrivDir,"rel1"),
+
+ %% Unpack and install P1H on client
+ P1HDir = filename:join([Root, "releases", "P1H"]),
+
+ %% The AppDirs argument (last arg to set_unpacked) below is really
+ %% not necessary, it could just be [] since the path is the same
+ %% as default. But it is given here in order to force hitting the
+ %% release_handler:check_path function so it can be checked that
+ %% it does not use file:read_file_info on the client node, see
+ %% trace_disallowed_calls/1 and check_disallowed_calls/0 below.
+ %% (OTP-9142)
+ {ok, "P1H"} = rpc:call(Node, release_handler, set_unpacked,
+ [filename:join(P1HDir, "rel1.rel"),
+ [{a,"1.0",filename:join(MasterDir,lib)}]]),
+
+ ?check_release_client(Node,"P1H",unpacked,["a-1.0"]),
+
+ ok = rpc:call(Node, release_handler, install_file,
+ ["P1H", filename:join(P1HDir, "start.boot")]),
+ ok = rpc:call(Node, release_handler, install_file,
+ ["P1H", filename:join(P1HDir, "sys.config")]),
+ ok = rpc:call(Node, release_handler, install_file,
+ ["P1H", filename:join(P1HDir, "relup")]),
+ ?print([{release_handler_state, Node},
+ rpc:call(Node, sys, get_status, [release_handler])]),
+
+ {ok,"P1G",[new_appl]} =
+ rpc:call(Node, release_handler, check_install_release, ["P1H"]),
+
+ {ok,"P1G",[new_appl]} =
+ rpc:call(Node, release_handler, install_release, ["P1H"]),
+
+ Apps = rpc:call(Node, application, which_applications, []),
+ {a,"A CXC 138 11","1.0"} = lists:keyfind(a, 1, Apps),
+ X = rpc:call(Node, a, a, []),
+ {key2, val2} = lists:keyfind(key2, 1, X),
+ {key1, val1} = lists:keyfind(key1, 1, X),
+
+ check_disallowed_calls(),
+ reboot(TestNode,Node),
+ trace_disallowed_calls(Node),
+
+ client1_2(TestNode,PrivDir,Node).
+
+client1_2(TestNode,PrivDir,Node) ->
+ ?print(["client1_2 start"]),
+
+ %% Check that P1H is still unpacked, install it and make_permanent
+ ?check_release_client(Node,"P1H",unpacked,["a-1.0"]),
+
+ {ok,"P1G",[new_appl]} =
+ rpc:call(Node, release_handler, install_release, ["P1H"]),
+ ?check_release_client(Node,"P1H",current,["a-1.0"]),
+ ?check_running_app_client(Node,a,"1.0"),
+
+ ok = rpc:call(Node, release_handler, make_permanent, ["P1H"]),
+
+ check_disallowed_calls(),
+ reboot(TestNode,Node),
+ trace_disallowed_calls(Node),
+
+ client1_3(TestNode,PrivDir,Node).
+
+client1_3(TestNode,PrivDir,Node) ->
+ ?print(["client1_3 start"]),
+
+ %% Check that P1H is permanent
+ ?check_release_client(Node,"P1H",permanent,["a-1.0"]),
+ X = rpc:call(Node, a, a, []),
+ {key2, val2} = lists:keyfind(key2, 1, X),
+ {key1, val1} = lists:keyfind(key1, 1, X),
+
+ %% Unpack P1I on master
+ {ok, "P1I"} = unpack_release(PrivDir,"rel2"),
+
+ MasterRoot = code:root_dir(),
+
+ %% Unpack and install P1I on client
+ P1IDir = filename:join([MasterRoot, "releases", "P1I"]),
+ {ok, "P1I"} = rpc:call(Node, release_handler, set_unpacked,
+ [filename:join(P1IDir, "rel2.rel"),[]]),
+
+ ?check_release_client(Node,"P1I",unpacked,["a-1.1"]),
+
+ ok = rpc:call(Node, release_handler, install_file,
+ ["P1I", filename:join(P1IDir, "start.boot")]),
+ ok = rpc:call(Node, release_handler, install_file,
+ ["P1I", filename:join(P1IDir, "sys.config")]),
+ ok = rpc:call(Node, release_handler, install_file,
+ ["P1I", filename:join(P1IDir, "relup")]),
+
+ {ok,"P1H",[{extra, gott}]} =
+ rpc:call(Node, release_handler, check_install_release, ["P1I"]),
+ {error,_} = rpc:call(Node, release_handler, check_install_release, ["P1J"]),
+ {ok,"P1H",[{extra, gott}]} =
+ rpc:call(Node, release_handler, install_release, ["P1I"]),
+
+ ?check_running_app_client(Node,a,"1.1"),
+ X2 = rpc:call(Node, a, a, []),
+ {key2, newval2} = lists:keyfind(key2, 1, X2),
+ {key1, val1} = lists:keyfind(key1, 1, X2),
+ {ok, bval} = rpc:call(Node, a, b, []),
+
+ %% Unpack P2A on master
+ {ok, "P2A"} = unpack_release(PrivDir,"rel3"),
+
+ %% Unpack and install P2A on client
+ P2ADir = filename:join([MasterRoot, "releases", "P2A"]),
+ {ok, "P2A"} =
+ rpc:call(Node, release_handler, set_unpacked,
+ [filename:join(P2ADir, "rel3.rel"),[]]),
+
+ ok = rpc:call(Node, release_handler, install_file,
+ ["P2A", filename:join(P2ADir, "start.boot")]),
+ ok = rpc:call(Node, release_handler, install_file,
+ ["P2A", filename:join(P2ADir, "sys.config")]),
+ ok = rpc:call(Node, release_handler, install_file,
+ ["P2A", filename:join(P2ADir, "relup")]),
+
+ {ok, "P1I", [new_emu]} =
+ rpc:call(Node, release_handler, check_install_release, ["P2A"]),
+ ok = rpc:call(Node, release_handler, make_permanent, ["P1I"]),
+ ?check_release_client(Node,"P1I",permanent,["a-1.1"]),
+
+ %% since the install_release below reboot the node...
+ check_disallowed_calls(),
+ cover_client(TestNode,Node,stop_cover),
+
+ {ok, "P1I", [new_emu]} =
+ rpc:call(Node, release_handler, install_release, ["P2A"]),
+ %% Reboots the client !
+
+ check_reboot(TestNode,Node),
+ trace_disallowed_calls(Node),
+
+ client1_4(TestNode,Node).
+
+client1_4(TestNode,Node) ->
+ ?print(["client1_4 start"]),
+
+ %% Check that P2A is in use.
+ ?check_release_client(Node,"P2A",current,["a-1.1"]),
+ ?check_running_app_client(Node,a,"1.1"),
+ X = rpc:call(Node, a, a, []),
+ {key2, newval2} = lists:keyfind(key2, 1, X),
+ {key1, val1} = lists:keyfind(key1, 1, X),
+ {ok, bval} = rpc:call(Node, a, b, []),
+
+ %% Reboot from P1I
+ check_disallowed_calls(),
+ reboot(TestNode,Node),
+ trace_disallowed_calls(Node),
+
+ client1_5(TestNode,Node).
+
+client1_5(TestNode,Node) ->
+ ?print(["client1_5 start"]),
+
+ %% Check that P1I is used
+ {ok, "P1I", [new_emu]} =
+ rpc:call(Node, release_handler, check_install_release, ["P2A"]),
+
+ %% since the install_release below will reboot the node...
+ check_disallowed_calls(),
+ cover_client(TestNode,Node,stop_cover),
+
+ %% Install P2A again
+ {ok, "P1I", [new_emu]} =
+ rpc:call(Node, release_handler, install_release, ["P2A"]),
+
+ %% We are rebooted again.
+ check_reboot(TestNode,Node),
+ trace_disallowed_calls(Node),
+
+ client1_6(TestNode,Node).
+
+client1_6(TestNode,Node) ->
+ ?print(["client1_6 start"]),
+
+ %% Check that P2A is used
+ ?check_release_client(Node,"P2A",current,["a-1.1"]),
+ ?check_running_app_client(Node,a,"1.1"),
+ X = rpc:call(Node, a, a, []),
+ {key2, newval2} = lists:keyfind(key2, 1, X),
+ {key1, val1} = lists:keyfind(key1, 1, X),
+ {ok, bval} = rpc:call(Node, a, b, []),
+
+ %% Make P2A permanent
+ ok = rpc:call(Node, release_handler, make_permanent, ["P2A"]),
+
+ %% Reboot from P2A
+ check_disallowed_calls(),
+ reboot(TestNode,Node),
+ trace_disallowed_calls(Node),
+
+ client1_7(TestNode,Node).
+
+client1_7(TestNode,Node) ->
+ ?print(["client1_7 start"]),
+
+ %% Check that P2A is used
+ ?check_release_client(Node,"P2A",permanent,["a-1.1"]),
+
+ %% since the reboot_old_release below will reboot the node
+ check_disallowed_calls(),
+ cover_client(TestNode,Node,stop_cover),
+
+ %% Install old P1H
+ rpc:call(Node, release_handler, reboot_old_release, ["P1H"]),
+ %% We are rebooted.
+ check_reboot(TestNode,Node),
+ trace_disallowed_calls(Node),
+
+ client1_8(TestNode,Node).
+
+client1_8(TestNode,Node) ->
+ ?print(["client1_8 start"]),
+
+ %% Check that P1H is permanent
+ ?check_release_client(Node,"P1H",permanent,["a-1.0"]),
+ X = rpc:call(Node, a, a, []),
+ {key2, val2} = lists:keyfind(key2, 1, X),
+ {key1, val1} = lists:keyfind(key1, 1, X),
+
+ %% Remove P1I and P2I from client
+ ok = rpc:call(Node, release_handler, set_removed, ["P2A"]),
+ ok = rpc:call(Node, release_handler, set_removed, ["P1I"]),
+
+ check_disallowed_calls(),
+ reboot(TestNode,Node),
+ trace_disallowed_calls(Node),
+
+ client1_9(TestNode,Node).
+
+client1_9(TestNode,Node) ->
+ ?print(["client1_9 start"]),
+
+ %% Check that P2A and P1I does not exists and that PiH is permanent.
+ Rels = rpc:call(Node, release_handler, which_releases, []),
+ false = lists:keysearch("P2A", 2, Rels),
+ false = lists:keysearch("P1I", 2, Rels),
+ ?check_release_client(Node,"P1H",permanent,["a-1.0"]),
+ X = rpc:call(Node, a, a, []),
+ {key2, val2} = lists:keyfind(key2, 1, X),
+ {key1, val1} = lists:keyfind(key1, 1, X),
+
+ %% since the reboot_old_release below will reboot the node
+ check_disallowed_calls(),
+ cover_client(TestNode,Node,stop_cover),
+
+ %% Install old P1G
+ rpc:call(Node, release_handler, reboot_old_release, ["P1G"]),
+ %% We are rebooted.
+ check_reboot(TestNode,Node),
+ trace_disallowed_calls(Node),
+
+ client1_10(TestNode,Node).
+
+client1_10(TestNode,Node) ->
+ ?print(["client1_10 start"]),
+
+ %% Check that P1G is permanent
+ ?check_release_client(Node,"P1G",permanent,[]),
+ ?check_release_client(Node,"P1H",old,["a-1.0"]),
+ {error,client_node} = rpc:call(Node,release_handler,remove_release,["P1H"]),
+ ok = rpc:call(Node, release_handler, set_removed, ["P1H"]),
+
+ check_disallowed_calls(),
+ reboot(TestNode,Node),
+ trace_disallowed_calls(Node),
+
+ client1_11(TestNode,Node).
+
+client1_11(TestNode,Node) ->
+ ?print(["client1_11 start"]),
+
+ %% Check that P1G is permanent
+ ?check_release_client(Node,"P1G",permanent,[]),
+
+ check_disallowed_calls(),
+ stop_client(TestNode,Node), %% TEST IS OK !!
+ net_kernel:monitor_nodes(false),
+
+ ok = release_handler:remove_release("P2A"),
+ ok = release_handler:remove_release("P1I"),
+ ok = release_handler:remove_release("P1H"),
+ ok.
+
+%% Start tracing of the file module on the client node. This module
+%% shall never be called, since
+%% 1) the node is a client from the release_handler's point of view,
+%% so all file access should be done via rpc calls to the master
+%% 2) it is started with erl_prim_loader loader set to 'inet' so all
+%% code loading should be done via the inet to the master
+%% (OTP-9142)
+%% This function is called each time the client node is started and to
+%% check if a call has been made, call check_disallowed_node/0
+trace_disallowed_calls(Node) ->
+ MasterProc = self(),
+ rpc:call(Node,dbg,tracer,[process,{fun(T,_) -> MasterProc ! T end,[]}]),
+ rpc:call(Node,dbg,p,[all,call]),
+ rpc:call(Node,dbg,tp,[file,[]]).
+
+check_disallowed_calls() ->
+ receive
+ Trace when element(1,Trace)==trace ->
+ exit({disallowed_function_call,Trace})
+ after 0 ->
+ ok
+ end.
+
+start_client(TestNode,Client) ->
+ {Start, Node} = do_start_client(Client,test_host()),
+ Cmd = lists:concat(["env NODENAME=",Client," ",
+ filename:join(code:root_dir(), Start)]),
+ ?print([{start_client,Client},Cmd]),
+ Res = os:cmd(Cmd),
+ ?print([{start_client,result},Res]),
+ receive
+ {nodeup, Node} ->
+ wait_started(TestNode,Node)
+ after 30000 ->
+ ?print([{start_client,failed,Node},net_adm:ping(Node)]),
+ ?fail({"can not start", Node})
+ end.
+
+do_start_client(Client, Host) ->
+ Node = list_to_atom(lists:concat([Client,"@",Host])),
+ Start = filename:join(["clients", "type1", Node, "bin", "start"]),
+ {Start, Node}.
+
+reboot(TestNode,Node) ->
+ cover_client(TestNode,Node,stop_cover),
+ rpc:call(Node, init, reboot, []),
+ check_reboot(TestNode,Node).
+
+%% This way of checking that the node is rebooted will only work if
+%% the nodes are automatically re-connected after the reboot. This
+%% happens for master/client (when sasl is started on the client).
+check_reboot(TestNode,Node) ->
+ receive
+ {nodedown, Node} ->
+ receive
+ {nodeup, Node} -> wait_started(TestNode,Node)
+ after 30000 ->
+ ?fail({Node, "not rebooted",net_adm:ping(Node)})
+ end
+ after 30000 ->
+ ?fail({Node, "not closing down",net_adm:ping(Node)})
+ end.
+
+stop_client(TestNode,Node) ->
+ cover_client(TestNode,Node,stop_cover),
+ rpc:call(Node, init, stop, []),
+ receive
+ {nodedown, Node} -> ok
+ after 30000 ->
+ ?fail({Node, "not stopping"})
+ end.
+
+wait_started(TestNode,Node) ->
+ case rpc:call(Node, init, get_status, []) of
+ {started, _} ->
+ cover_client(TestNode,Node,start_cover),
+ Node;
+ _ ->
+ timer:sleep(1000),
+ wait_started(TestNode,Node)
+ end.
+
+cover_client(TestNode,Node,Func) ->
+ R = rpc:call(TestNode,release_handler_SUITE,Func,[Node]),
+ ?print([{Func,Node,R}]).
+
+
+%%-----------------------------------------------------------------
+%% This test starts a client node which uses this node as master
+%% for the release_handler.
+%% The client node has the name cli2@HOSTNAME.
+%% The client node is not allowed to do ANY release updates
+%% as it also have another (non-existing) master node.
+%%
+%% The to_erl /tmp/cli2@HOSTNAME/ command can be used to connect
+%% to the client node.
+%% run_erl logs for the client can be found in the directory:
+%% code:root_dir() ++ "/clients/type1/cli2@HOSTNAME/log
+%%-----------------------------------------------------------------
+client2(TestNode,PrivDir,ClientSname) ->
+ TestHost = test_host(),
+ ?print(["client2 start"]),
+
+ %% Clean up if previous test case failed
+ release_handler:remove_release("P1H"),
+
+ ok = net_kernel:monitor_nodes(true),
+ Node = start_client(TestNode,ClientSname),
+
+ %% Check env var for SASL on client node
+ ?print([{sasl_env, Node}, rpc:call(Node, application, get_all_env, [sasl])]),
+ SaslEnv = rpc:call(Node, application, get_all_env, [sasl]),
+ {_,CliDir} = lists:keyfind(client_directory,1,SaslEnv),
+ {_,[Master,Master2]} = lists:keyfind(masters,1,SaslEnv),
+ {_,StartCli} = lists:keyfind(start_prg,1,SaslEnv),
+ Root = code:root_dir(),
+ true = (CliDir =:= filename:join([Root,"clients","type1",Node])),
+ true = (StartCli =:= filename:join([CliDir,"bin","start"])),
+ true = (Master =:= node()),
+ true = (Master2 =:= list_to_atom("master2@"++TestHost)),
+
+ {ok, "P1H"} = unpack_release(PrivDir,"rel1"),
+
+ {error,{bad_masters,[Master2]}} =
+ rpc:call(Node, release_handler, set_unpacked,
+ [filename:join([Root, "releases", "P1H", "rel1.rel"]),[]]),
+
+ {error,{no_such_release,"P1H"}} =
+ rpc:call(Node, release_handler, check_install_release, ["P1H"]),
+
+ stop_client(TestNode,Node), %% TEST IS OK !!
+ net_kernel:monitor_nodes(false),
+
+ release_handler:remove_release("P1H"),
+ ok.
+
+
+stop(Now) ->
+ %% The timestamp is only used for debugging. It is printed by
+ %% release_handler_SUITE also.
+ R = init:stop(),
+ erlang:display({init_stop,Now,R}),
+ R.
+
+unpack_p1h(TestNode,PrivDir) ->
+ {ok, "P1H"} = unpack_release(PrivDir,"rel1"),
+ ?check_release("P1H",unpacked,["a-1.0"]),
+ ok.
+
+permanent_p1h(TestNode) ->
+ ?check_release("P1H",unpacked,["a-1.0"]),
+ {ok,"P1G",[new_appl]} = release_handler:install_release("P1H"),
+ ?check_release("P1H",current,["a-1.0"]),
+ ok = release_handler:make_permanent("P1H"),
+ ?check_release("P1H",permanent,["a-1.0"]),
+ ok.
+
+
+reg_proc(Name) ->
+ catch unregister(Name),
+ Pid = spawn_link(?MODULE, registered_loop, [Name]),
+ global:register_name(Name, Pid),
+ ok.
+
+registered_loop(_Name) ->
+ receive
+ kill ->
+ exit(killed)
+ end.
+
+check_release(TestNode,Node,Vsn,Status,Apps,Line) ->
+ case rpc:call(Node,release_handler,which_releases,[]) of
+ {badrpc,_}=Error ->
+ ?fail_line(Line,{check_release,Node,Vsn,Status,Error});
+ Rels ->
+ ?print_line(Line,["check_release:", Rels]),
+ {"SASL-test", Vsn, Libs, Status} = lists:keyfind(Vsn, 2, Rels),
+ true = lists:all(fun(App) -> lists:member(App,Libs) end,Apps),
+ ok
+ end.
+
+check_running_app(TestNode,Node,App,Vsn,Line) ->
+ case rpc:call(Node,application,which_applications,[]) of
+ {badrpc,_}=Error ->
+ ?fail_line(Line,{check_running_app,Node,App,Vsn,Error});
+ Apps ->
+ ?print_line(Line,["check_running_app:", Apps]),
+ {App, _, Vsn} = lists:keyfind(a, 1, Apps),
+ ok
+ end.
+
+test_host() ->
+ {ok,Host} = inet:gethostname(),
+ Host.
+
+unpack_release(PrivDir,Rel) ->
+ copy(filename:join([PrivDir,Rel,Rel++".tar.gz"]),
+ filename:join(code:root_dir(),releases)),
+ release_handler:unpack_release(Rel).
+
+copy(Src, DestDir) ->
+ Dest = filename:join(DestDir, filename:basename(Src)),
+ {ok,_} = file:copy(Src, Dest),
+ ok.
+
diff --git a/lib/sasl/test/overload_SUITE.erl b/lib/sasl/test/overload_SUITE.erl
new file mode 100644
index 0000000000..92b1aaed6e
--- /dev/null
+++ b/lib/sasl/test/overload_SUITE.erl
@@ -0,0 +1,175 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2011. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+-module(overload_SUITE).
+-include("test_server.hrl").
+
+-compile(export_all).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+all() -> [info, set_config_data, set_env_vars, request, timeout].
+all(suite) -> all().
+
+init_per_testcase(_Case,Config) ->
+ restart_sasl(),
+ Config.
+
+end_per_testcase(Case,Config) ->
+ try apply(?MODULE,Case,[cleanup,Config])
+ catch error:undef -> ok
+ end,
+ ok.
+
+%%%-----------------------------------------------------------------
+info(suite) -> [];
+info(_Config) ->
+ ?line Info = overload:get_overload_info(),
+ ?line [{total_intensity,0.0},
+ {accept_intensity,0.0},
+ {max_intensity,0.8},
+ {weight,0.1},
+ {total_requests,0},
+ {accepted_requests,0}] = Info.
+
+%%%-----------------------------------------------------------------
+set_config_data(suite) -> [];
+set_config_data(_Config) ->
+ ?line InfoDefault = overload:get_overload_info(),
+ ?line ok = check_info(0.8,0.1,InfoDefault),
+ ?line ok = overload:set_config_data(0.5,0.4),
+ ?line Info1 = overload:get_overload_info(),
+ ?line ok = check_info(0.5,0.4,Info1),
+ ok.
+
+%%%-----------------------------------------------------------------
+set_env_vars(suite) -> [];
+set_env_vars(_Config) ->
+ ?line InfoDefault = overload:get_overload_info(),
+ ?line ok = check_info(0.8,0.1,InfoDefault),
+ ?line ok = application:set_env(sasl,overload_max_intensity,0.5),
+ ?line ok = application:set_env(sasl,overload_weight,0.4),
+ ?line ok = application:stop(sasl),
+ ?line ok = application:start(sasl),
+ ?line Info1 = overload:get_overload_info(),
+ ?line ok = check_info(0.5,0.4,Info1),
+ ok.
+set_env_vars(cleanup,_Config) ->
+ application:unset_env(sasl,overload_max_intensity),
+ application:unset_env(sasl,overload_weight),
+ ok.
+
+%%%-----------------------------------------------------------------
+request(suite) -> [];
+request(_Config) ->
+ %% Find number of request that can be done with default settings
+ %% and no delay
+ ?line overload:set_config_data(0.8, 0.1),
+ ?line NDefault = do_many_requests(0),
+ ?line restart_sasl(),
+ ?line ?t:format("NDefault: ~p",[NDefault]),
+
+ %% Check that the number of requests increases when max_intensity
+ %% increases
+ ?line overload:set_config_data(2, 0.1),
+ ?line NLargeMI = do_many_requests(0),
+ ?line restart_sasl(),
+ ?line ?t:format("NLargeMI: ~p",[NLargeMI]),
+ ?line true = NLargeMI > NDefault,
+
+ %% Check that the number of requests decreases when weight
+ %% increases
+ ?line overload:set_config_data(0.8, 1),
+ ?line NLargeWeight = do_many_requests(0),
+ ?line restart_sasl(),
+ ?line ?t:format("NLargeWeight: ~p",[NLargeWeight]),
+ ?line true = NLargeWeight < NDefault,
+
+ %% Check that number of requests increases when delay between
+ %% requests increases.
+ %% (Keeping same config and comparing to large weight in order to
+ %% minimize the time needed for this case.)
+ ?line overload:set_config_data(0.8, 1),
+ ?line NLargeTime = do_many_requests(500),
+ ?line restart_sasl(),
+ ?line ?t:format("NLargeTime: ~p",[NLargeTime]),
+ ?line true = NLargeTime > NLargeWeight,
+ ok.
+
+%%%-----------------------------------------------------------------
+timeout(suite) -> [];
+timeout(_Config) ->
+ ?line overload:set_config_data(0.8, 1),
+ ?line _N = do_many_requests(0),
+
+ %% Check that the overload alarm is raised
+ ?line [{overload,_}] = alarm_handler:get_alarms(),
+
+ %% Fake a clear timeout in overload.erl and check that, since it
+ %% came very soon after the overload situation, the alarm is not
+ %% cleared
+ ?line overload ! timeout,
+ ?line timer:sleep(1000),
+ ?line [{overload,_}] = alarm_handler:get_alarms(),
+
+ %% A bit later, try again and check that this time the alarm is
+ %% cleared
+ ?line overload ! timeout,
+ ?line timer:sleep(1000),
+ ?line [] = alarm_handler:get_alarms(),
+
+ ok.
+
+
+%%%-----------------------------------------------------------------
+%%% INTERNAL FUNCTIONS
+
+%%%-----------------------------------------------------------------
+%%% Call overload:request/0 up to 30 times with the given time delay
+%%% between. Stop when 'reject' is returned.
+do_many_requests(T) ->
+ 30 - do_requests(30,T).
+
+do_requests(0,_) ->
+ ?t:fail(never_rejected);
+do_requests(N,T) ->
+ case overload:request() of
+ accept ->
+ timer:sleep(T),
+ do_requests(N-1,T);
+ reject ->
+ N
+ end.
+
+%%%-----------------------------------------------------------------
+%%% Restart the sasl application
+restart_sasl() ->
+ application:stop(sasl),
+ application:start(sasl),
+ ok.
+
+%%%-----------------------------------------------------------------
+%%% Check that max_intensity and weight is set as expected
+check_info(MI,W,Info) ->
+ case {lists:keyfind(max_intensity,1,Info), lists:keyfind(weight,1,Info)} of
+ {{_,MI},{_,W}} -> ok;
+ _ -> ?t:fail({unexpected_info,MI,W,Info})
+ end.
+
+
diff --git a/lib/sasl/test/rb_SUITE.erl b/lib/sasl/test/rb_SUITE.erl
new file mode 100644
index 0000000000..b53c382609
--- /dev/null
+++ b/lib/sasl/test/rb_SUITE.erl
@@ -0,0 +1,606 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2011. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+-module(rb_SUITE).
+-include("test_server.hrl").
+
+-compile(export_all).
+
+-define(SUP,rb_SUITE_sup).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+all() ->
+ no_group_cases() ++ [{group,running_error_logger}].
+
+no_group_cases() ->
+ [help,
+ start_error_stop].
+
+groups() ->
+ [{running_error_logger,[shuffle],[show,
+ list,
+ rescan,
+ start_stop_log,
+ grep,
+ filter_filter,
+ filter_date,
+ filter_filter_and_date,
+ filter_re_no
+ ]}].
+
+
+all(suite) ->
+ no_group_cases() ++
+ [{conf,
+ install_mf_h,
+ element(3,lists:keyfind(running_error_logger,1,groups())),
+ remove_mf_h}
+ ].
+
+
+init_per_suite(Config) ->
+ ?line PrivDir = ?config(priv_dir,Config),
+ ?line RbDir = filename:join(PrivDir,rb),
+ ?line ok = file:make_dir(RbDir),
+ NewConfig = [{rb_dir,RbDir}|Config],
+ reset_sasl(NewConfig),
+ NewConfig.
+
+end_per_suite(_Config) ->
+ ok.
+
+init_per_group(running_error_logger,Config) ->
+ install_mf_h(Config).
+
+end_per_group(running_error_logger,Config) ->
+ remove_mf_h(Config).
+
+init_per_testcase(_Case,Config) ->
+ case whereis(?SUP) of
+ undefined -> ok;
+ Pid -> kill(Pid)
+ end,
+ empty_error_logs(Config),
+ Config.
+
+kill(Pid) ->
+ Ref = erlang:monitor(process,Pid),
+ exit(Pid,kill),
+ receive {'DOWN', Ref, process, Pid, _Info} -> ok end.
+
+end_per_testcase(Case,Config) ->
+ try apply(?MODULE,Case,[cleanup,Config])
+ catch error:undef -> ok
+ end,
+ ok.
+
+
+%%%-----------------------------------------------------------------
+
+help() -> help(suite).
+help(suite) -> [];
+help(_Config) ->
+ ?line Help = capture(fun() -> rb:h() end),
+ ?line "Report Browser Tool - usage" = hd(Help),
+ ?line "rb:stop - stop the rb_server" = lists:last(Help),
+ ok.
+
+
+start_error_stop() -> start_error_stop(suite).
+start_error_stop(suite) -> [];
+start_error_stop(Config) ->
+ ?line RbDir = ?config(rb_dir,Config),
+
+ ?line {error,{"cannot locate report directory",_}} = rb:start(),
+
+
+ ?line ok = application:set_env(sasl,error_logger_mf_dir,"invaliddir"),
+ ?line ok = application:set_env(sasl,error_logger_mf_maxbytes,1000),
+ ?line ok = application:set_env(sasl,error_logger_mf_maxfiles,2),
+ ?line restart_sasl(),
+ ?line {error,{"cannot read the index file",_}} = rb:start(),
+ ?line ok = application:set_env(sasl,error_logger_mf_dir,RbDir),
+ ?line restart_sasl(),
+ ?line {ok,_} = rb:start(),
+
+ ?line ok = rb:stop(),
+ ok.
+
+
+%% start_opts(suite) -> [];
+%% start_opts(Config) ->
+%% PrivDir = ?config(priv_dir,Config),
+%% RbDir = filename:join(PrivDir,rb_opts),
+%% ok = file:make_dir(RbDir),
+
+
+install_mf_h(Config) ->
+ ?line RbDir = ?config(rb_dir,Config),
+ ?line ok = application:set_env(sasl,error_logger_mf_dir,RbDir),
+ ?line ok = application:set_env(sasl,error_logger_mf_maxbytes,5000),
+ ?line ok = application:set_env(sasl,error_logger_mf_maxfiles,2),
+ ?line restart_sasl(),
+ Config.
+
+remove_mf_h(_Config) ->
+ ok.
+
+
+
+show() -> show(suite).
+show(suite) -> [];
+show(Config) ->
+ ?line PrivDir = ?config(priv_dir,Config),
+ ?line OutFile = filename:join(PrivDir,"rb_SUITE_log.txt"),
+
+ %% Insert some reports in the error log and start rb
+ init_error_logs(),
+ ?line ok = start_rb(OutFile),
+
+ %% Show all reports
+ ?line All = check_report(fun() -> rb:show() end,OutFile),
+
+ %% Show by number
+ ?line [{_,First}] = check_report(fun() -> rb:show(1) end,OutFile),
+ ?line {1,First} = lists:keyfind(1,1,All),
+
+ %% Show by type
+ ?line [{_,CR}] = check_report(fun() -> rb:show(crash_report) end,OutFile),
+ ?line true = contains(CR,"rb_test_crash"),
+ ?line [{_,EC},{_,EM}] = check_report(fun() -> rb:show(error) end,OutFile),
+ ?line true = contains(EC,"rb_test_crash"),
+ ?line true = contains(EM,"rb_test_error_msg"),
+ ?line [{_,ER}] = check_report(fun() -> rb:show(error_report) end,OutFile),
+ ?line true = contains(ER,"rb_test_error"),
+ ?line [{_,IR}] = check_report(fun() -> rb:show(info_report) end,OutFile),
+ ?line true = contains(IR,"rb_test_info"),
+ ?line [{_,IM}] = check_report(fun() -> rb:show(info_msg) end,OutFile),
+ ?line true = contains(IM,"rb_test_info_msg"),
+ ?line [_|_] = check_report(fun() -> rb:show(progress) end,OutFile),
+ ?line [{_,SR}] = check_report(fun() -> rb:show(supervisor_report) end,
+ OutFile),
+ ?line true = contains(SR,"child_terminated"),
+ ?line true = contains(SR,"{rb_SUITE,rb_test_crash}"),
+
+ ok.
+
+list() -> list(suite).
+list(suite) -> [];
+list(Config) ->
+ ?line PrivDir = ?config(priv_dir,Config),
+ ?line OutFile = filename:join(PrivDir,"rb_SUITE_log.txt"),
+
+ %% Insert some reports in the error log and start rb
+ init_error_logs(),
+ ?line ok = start_rb(OutFile),
+
+ ?line All = capture(fun() -> rb:list() end),
+ ?line [{crash_report,[_]=CR},
+ {error,[_,_]=EM},
+ {error_report,[_]=ER},
+ {info_msg,[_]=IM},
+ {info_report,[_]=IR},
+ {progress,[_|_]=P},
+ {supervisor_report,[_]=SR}] = sort_list(All),
+
+ ?line [{crash_report,CR}] =
+ sort_list(capture(fun() -> rb:list(crash_report) end)),
+ ?line [{error,EM}] =
+ sort_list(capture(fun() -> rb:list(error) end)),
+ ?line [{error_report,ER}] =
+ sort_list(capture(fun() -> rb:list(error_report) end)),
+ ?line [{info_msg,IM}] =
+ sort_list(capture(fun() -> rb:list(info_msg) end)),
+ ?line [{info_report,IR}] =
+ sort_list(capture(fun() -> rb:list(info_report) end)),
+ ?line [{progress,P}] =
+ sort_list(capture(fun() -> rb:list(progress) end)),
+ ?line [{supervisor_report,SR}] =
+ sort_list(capture(fun() -> rb:list(supervisor_report) end)),
+
+ ok.
+
+
+grep() -> grep(suite).
+grep(suite) -> [];
+grep(Config) ->
+ ?line PrivDir = ?config(priv_dir,Config),
+ ?line OutFile = filename:join(PrivDir,"rb_SUITE_log.txt"),
+
+ %% Insert some reports in the error log and start rb
+ init_error_logs(),
+ ?line ok = start_rb(OutFile),
+
+ ?line [{_,S},
+ {_,CR},
+ {_,EC},
+ {_,IM},
+ {_,IR},
+ {_,EM},
+ {_,ER}]= check_report(fun() -> rb:grep("rb_test_") end,OutFile),
+ ?line true = contains(S, "rb_test_crash"),
+ ?line true = contains(CR, "rb_test_crash"),
+ ?line true = contains(EC, "rb_test_crash"),
+ ?line true = contains(IM, "rb_test_info_msg"),
+ ?line true = contains(IR, "rb_test_info"),
+ ?line true = contains(EM, "rb_test_error_msg"),
+ ?line true = contains(ER, "rb_test_error"),
+ ok.
+
+
+filter_filter() -> filter_filter(suite).
+filter_filter(suite) -> [];
+filter_filter(Config) ->
+ ?line PrivDir = ?config(priv_dir,Config),
+ ?line OutFile = filename:join(PrivDir,"rb_SUITE_log.txt"),
+
+ %% Insert some reports in the error log and start rb
+ init_error_logs(),
+ ?line ok = start_rb(OutFile),
+
+ ?line All = check_report(fun() -> rb:show() end,OutFile),
+
+ ?line ER = [_] = rb_filter([{rb_SUITE,rb_test_error}],OutFile),
+ ?line [] = rb_filter([{rb_SUITE,rb_test}],OutFile),
+ ?line _E = [_,_] = rb_filter([{rb_SUITE,"rb_test",re}],OutFile),
+ ?line AllButER = rb_filter([{rb_SUITE,rb_test_error,no}],OutFile),
+
+ {_,AllRep} = lists:unzip(All),
+ {_,ERRep} = lists:unzip(ER),
+ {_,AllButERRep} = lists:unzip(AllButER),
+ ?line AllButERRep = AllRep -- ERRep,
+
+ ok.
+
+filter_date() -> filter_date(suite).
+filter_date(suite) -> [];
+filter_date(Config) ->
+ ?line PrivDir = ?config(priv_dir,Config),
+ ?line OutFile = filename:join(PrivDir,"rb_SUITE_log.txt"),
+
+
+ %% Insert some reports in the error log and start rb
+ init_error_logs(),
+ Between1 = calendar:local_time(),
+ timer:sleep(1000),
+ Between2 = calendar:local_time(),
+ ?line ok = start_rb(OutFile),
+
+ ?line All = check_report(fun() -> rb:show() end,OutFile),
+
+ Before = calendar:gregorian_seconds_to_datetime(
+ calendar:datetime_to_gregorian_seconds(calendar:local_time()) - 10),
+ After = calendar:gregorian_seconds_to_datetime(
+ calendar:datetime_to_gregorian_seconds(calendar:local_time()) + 1),
+
+ ?line All = rb_filter([],{Before,from},OutFile),
+ ?line All = rb_filter([],{After,to},OutFile),
+ ?line [] = rb_filter([],{Before,to},OutFile),
+ ?line [] = rb_filter([],{After,from},OutFile),
+ ?line All = rb_filter([],{Before,After},OutFile),
+
+ %%?t:format("~p~n",[All]),
+ ?line AllButLast = [{N-1,R} || {N,R} <- tl(All)],
+ ?line AllButLast = rb_filter([],{Before,Between1},OutFile),
+
+ ?line Last = hd(All),
+ ?line [Last] = rb_filter([],{Between2,After},OutFile),
+
+ ok.
+
+filter_filter_and_date() -> filter_filter_and_date(suite).
+filter_filter_and_date(suite) -> [];
+filter_filter_and_date(Config) ->
+ ?line PrivDir = ?config(priv_dir,Config),
+ ?line OutFile = filename:join(PrivDir,"rb_SUITE_log.txt"),
+
+
+ %% Insert some reports in the error log and start rb
+ init_error_logs(),
+ Between1 = calendar:local_time(),
+ timer:sleep(1000),
+ Between2 = calendar:local_time(),
+ ?line error_logger:error_report([{rb_SUITE,rb_test_filter}]),
+ ?line ok = start_rb(OutFile),
+
+ Before = calendar:gregorian_seconds_to_datetime(
+ calendar:datetime_to_gregorian_seconds(calendar:local_time()) - 10),
+ After = calendar:gregorian_seconds_to_datetime(
+ calendar:datetime_to_gregorian_seconds(calendar:local_time()) + 1),
+
+ ?line All = check_report(fun() -> rb:show() end,OutFile),
+ ?line Last = hd(All),
+
+ ?line [_,_,_] = rb_filter([{rb_SUITE,"rb_test",re}],{Before,After},OutFile),
+ ?line [_,_] = rb_filter([{rb_SUITE,"rb_test",re}],{Before,Between1},OutFile),
+ ?line [_] = rb_filter([{rb_SUITE,"rb_test",re}],{Between2,After},OutFile),
+ ?line [_] = rb_filter([{rb_SUITE,rb_test_filter}],{Before,After},OutFile),
+ ?line [] = rb_filter([{rb_SUITE,rb_test_filter}],{Before,Between1},OutFile),
+ ?line [Last] = rb_filter([{rb_SUITE,rb_test_filter,no}],{Between2,After},OutFile),
+ ?line {_,Str} = Last,
+ ?line false = contains(Str,"rb_test_filter"),
+
+ ok.
+
+
+filter_re_no() -> filter_re_no(suite).
+filter_re_no(suite) -> [];
+filter_re_no(Config) ->
+ ?line PrivDir = ?config(priv_dir,Config),
+ ?line OutFile = filename:join(PrivDir,"rb_SUITE_log.txt"),
+
+ %% Insert some reports in the error log and start rb
+ init_error_logs(),
+ ?line ok = start_rb(OutFile),
+
+ ?line All = check_report(fun() -> rb:show() end,OutFile),
+
+ ?line E = [_,_] = rb_filter([{rb_SUITE,"rb_test",re}],OutFile),
+ ?line AllButE = rb_filter([{rb_SUITE,"rb_test",re,no}],OutFile),
+
+ {_,AllRep} = lists:unzip(All),
+ {_,ERep} = lists:unzip(E),
+ {_,AllButERep} = lists:unzip(AllButE),
+ ?line AllButERep = AllRep -- ERep,
+
+ ok.
+
+
+rescan() -> rescan(suite).
+rescan(suite) -> [];
+rescan(Config) ->
+ ?line PrivDir = ?config(priv_dir,Config),
+ ?line OutFile = filename:join(PrivDir,"rb_SUITE_log.txt"),
+
+ %% Start rb
+ ?line ok = start_rb(OutFile),
+
+ %% Insert one more report and check that the list is longer. Note
+ %% that there might be two more reports, since the progress report
+ %% from starting rb_server might not be included before the rescan.
+ ?line AllBefore = capture(fun() -> rb:list() end),
+ ?line error_logger:error_report([{rb_SUITE,rb_test_rescan}]),
+ ?line ok = rb:rescan(),
+ ?line AllAfter = capture(fun() -> rb:list() end),
+ ?line Diff = length(AllAfter) - length(AllBefore),
+ ?line true = (Diff >= 1),
+
+ ok.
+
+
+start_stop_log() -> start_stop_log(suite).
+start_stop_log(suite) -> [];
+start_stop_log(Config) ->
+ ?line PrivDir = ?config(priv_dir,Config),
+ ?line OutFile = filename:join(PrivDir,"rb_SUITE_log.txt"),
+ ?line ok = file:write_file(OutFile,[]),
+
+ %% Start rb and check that show is printed to standard_io
+ ?line ok = start_rb(),
+ ?line StdioResult = [_|_] = capture(fun() -> rb:show(1) end),
+ ?line {ok,<<>>} = file:read_file(OutFile),
+
+ %% Start log and check that show is printed to log and not to standad_io
+ ?line ok = rb:start_log(OutFile),
+ ?line [] = capture(fun() -> rb:show(1) end),
+ ?line {ok,Bin} = file:read_file(OutFile),
+ ?line true = (Bin =/= <<>>),
+
+ %% Stop log and check that show is printed to standard_io and not to log
+ ?line ok = rb:stop_log(),
+ ?line ok = file:write_file(OutFile,[]),
+ ?line StdioResult = capture(fun() -> rb:show(1) end),
+ ?line {ok,<<>>} = file:read_file(OutFile),
+
+ %% Test that standard_io is used if log file can not be opened
+ ?line ok = rb:start_log(filename:join(nonexistingdir,"newfile.txt")),
+ ?line StdioResult = capture(fun() -> rb:show(1) end),
+ ?line {ok,<<>>} = file:read_file(OutFile),
+
+ ok.
+
+
+%%%-----------------------------------------------------------------
+%%% INTERNAL FUNCTIONS
+
+restart_sasl() ->
+ application:stop(sasl),
+ ok = application:start(sasl),
+ wait_for_sasl().
+
+reset_sasl(Config) ->
+ application:unset_env(sasl,error_logger_mf_dir),
+ application:unset_env(sasl,error_logger_mf_maxbytes),
+ application:unset_env(sasl,error_logger_mf_maxfiles),
+ empty_error_logs(Config).
+
+empty_error_logs(Config) ->
+ application:stop(sasl),
+ catch delete_content(?config(rb_dir, Config)),
+ ok = application:start(sasl),
+ wait_for_sasl().
+
+wait_for_sasl() ->
+ wait_for_sasl(50).
+wait_for_sasl(0) ->
+ ?t:fail("sasl application did not start within 5 seconds");
+wait_for_sasl(N) ->
+ case lists:keymember(sasl,1,application:which_applications()) of
+ true ->
+ ok;
+ false ->
+ timer:sleep(100),
+ wait_for_sasl(N-1)
+ end.
+
+start_rb(OutFile) ->
+ do_start_rb([{start_log,OutFile}]).
+start_rb() ->
+ do_start_rb([]).
+
+do_start_rb(Opts) ->
+ {ok,Pid} = rb:start(Opts),
+
+ %% Wait for process to started, then wait a little bit more
+ sys:get_status(Pid),
+ timer:sleep(500),
+
+ %% Make sure printouts (e.g. from rb:list(), come to the test log,
+ %% and that they can be captured.
+ group_leader(group_leader(),Pid),
+ ok.
+
+
+delete_tree(Dir) ->
+ case filelib:is_dir(Dir) of
+ true ->
+ delete_content(Dir),
+ file:del_dir(Dir);
+ false ->
+ ok = file:delete(Dir)
+ end.
+
+delete_content(Dir) ->
+ {ok,Files} = file:list_dir(Dir),
+ lists:foreach(fun(File) -> delete_tree(filename:join(Dir,File)) end,
+ Files).
+
+init_error_logs() ->
+ ?line error_logger:error_report([{rb_SUITE,rb_test_error}]),
+ ?line error_logger:error_msg("rb_test_error_msg"),
+ ?line error_logger:info_report([{rb_SUITE,rb_test_info}]),
+ ?line error_logger:info_msg("rb_test_info_msg"),
+ ?line _Pid = start(),
+ ?line Ref = erlang:monitor(process,?MODULE),
+ ?line gen_server:cast(?MODULE,crash),
+ ?line receive {'DOWN',Ref,process,_,{rb_SUITE,rb_test_crash}} -> ok
+ after 2000 ->
+ ?t:format("Got: ~p~n",[process_info(self(),messages)]),
+ ?t:fail("rb_SUITE server never died")
+ end,
+ ?line erlang:demonitor(Ref),
+ ?line wait_for_server(),
+ ok.
+
+wait_for_server() ->
+ case whereis(?MODULE) of
+ undefined ->
+ wait_for_server();
+ Pid ->
+ timer:sleep(100), % allow the supervisor report to be written
+ Pid
+ end.
+
+capture(Fun) ->
+ ?t:capture_start(),
+ ok = Fun(),
+ timer:sleep(1000),
+ ?t:capture_stop(),
+ string:tokens(lists:append(?t:capture_get()),"\n").
+
+
+rb_filter(Filter,OutFile) ->
+ check_report(fun() -> rb:filter(Filter) end, OutFile).
+rb_filter(Filter,Dates,OutFile) ->
+ check_report(fun() -> rb:filter(Filter,Dates) end, OutFile).
+
+
+%% This function first empties the given report file, then executes
+%% the fun and returns a list of {N,Report}, where Report is a report
+%% read from the file and N is an integer. The newest report has the
+%% lowest number.
+%% If the fun was a call to rb:show() (i.e. with no arguments), then
+%% the numbering (N) will be the same as rb's own numbering (as shown
+%% by rb:list()).
+check_report(Fun,File) ->
+ file:delete(File),
+ rb:rescan([{start_log,File}]),
+ ok = Fun(),
+ {ok,Bin} = file:read_file(File),
+ Reports = split_reports(binary_to_list(Bin),[],[]),
+ lists:zip(lists:seq(1,length(Reports)),Reports).
+
+-define(report_header_line,"\n===============================================================================\n").
+split_reports([],Report,Reports) ->
+ add_report(Report,Reports);
+split_reports(Text,Report,Reports) ->
+ case Text of
+ ?report_header_line++Rest ->
+ {Heading,PrevReport} = lists:splitwith(fun($\n) -> false;
+ (_) -> true
+ end,
+ Report),
+ split_reports(Rest,
+ ?report_header_line++Heading,
+ add_report(PrevReport,Reports));
+ [Ch|Rest] ->
+ split_reports(Rest,[Ch|Report],Reports)
+ end.
+
+add_report(Report,Reports) ->
+ case string:strip(Report,both,$\n) of
+ [] -> Reports;
+ Report1 -> [lists:reverse(Report1)|Reports]
+ end.
+
+%% Returns true if Substr is a substring of Str.
+contains(Str,Substr) ->
+ 0 =/= string:str(Str,Substr).
+
+%% Sort the result of rb_list after report type
+sort_list(List) ->
+ sort_list(List,dict:new()).
+sort_list([H|T],D) ->
+ case re:run(H,"\s+[0-9]+\s+([a-z_]+)",[{capture,all_but_first,list}]) of
+ nomatch ->
+ sort_list(T,D);
+ {match,[TypeStr]} ->
+ sort_list(T,dict:append(list_to_atom(TypeStr),H,D))
+ end;
+sort_list([],D) ->
+ lists:sort(dict:to_list(D)).
+
+
+%%%-----------------------------------------------------------------
+%%% A dummy supervisor and gen_server used for creating crash- and
+%%% supervisor reports
+start() ->
+ {ok,Pid} =
+ supervisor:start_link({local, ?SUP}, ?MODULE, i_am_supervisor),
+ unlink(Pid),
+ Pid.
+start_server() ->
+ gen_server:start_link({local, ?MODULE}, ?MODULE, i_am_server, []).
+init(i_am_server) ->
+ {ok, state};
+init(i_am_supervisor) ->
+ AChild = {?SUP,{?MODULE,start_server,[]},
+ permanent,2000,worker,[?MODULE]},
+ {ok,{{one_for_all,1,1}, [AChild]}}.
+handle_call(_Request, _From, State) ->
+ Reply = ok,
+ {reply, Reply, State}.
+handle_cast(crash, State) ->
+ exit({rb_SUITE,rb_test_crash}),
+ {noreply, State}.
+handle_info(_Info, State) ->
+ {noreply, State}.
+terminate(_Reason, _State) ->
+ ok.
+
diff --git a/lib/sasl/test/release_handler_SUITE.erl b/lib/sasl/test/release_handler_SUITE.erl
new file mode 100644
index 0000000000..efa775f344
--- /dev/null
+++ b/lib/sasl/test/release_handler_SUITE.erl
@@ -0,0 +1,1651 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2011. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(release_handler_SUITE).
+
+-include_lib("common_test/include/ct.hrl").
+
+-compile(export_all).
+
+% Default timetrap timeout (set in init_per_testcase).
+%-define(default_timeout, ?t:minutes(40)).
+-define(default_timeout, ?t:minutes(10)).
+
+suite() ->
+ [{ct_hooks, [ts_install_cth]}].
+
+init_per_suite(Config) ->
+ application:start(sasl),
+ Config.
+
+end_per_suite(_Config) ->
+ ok.
+
+all() ->
+ case os:type() of
+ {unix, _} -> unix_cases();
+ {win32, _} -> win32_cases()
+ end.
+
+unix_cases() ->
+ RunErl = filename:join([code:root_dir(),"bin","run_erl"]),
+ RunErlCases = case filelib:is_file(RunErl) of
+ true -> [{group, release}];
+ false -> [no_run_erl]
+ end,
+ [target_system] ++ RunErlCases ++ cases().
+
+win32_cases() ->
+ cases().
+
+%% Cases that can be run on all platforms
+cases() ->
+ [otp_2740, otp_2760, otp_5761, instructions, eval_appup].
+
+groups() ->
+ [{release,[],
+ [
+ {group,release_single},
+ {group,release_gg}
+ ]},
+ {release_single,[],
+ [
+ upgrade,
+ client1,
+ client2
+ ]},
+ {release_gg,[],
+ [
+ upgrade_gg
+ ]}].
+
+%% {group,release}
+%% Top group for all cases using run_erl
+init_per_group(release, Config) ->
+ Dog = ?t:timetrap(?default_timeout),
+ P1gInstall = filename:join(priv_dir(Config),p1g_install),
+ ok = do_create_p1g(Config,P1gInstall),
+ ok = create_p1h(Config),
+ ?t:timetrap_cancel(Dog);
+
+%% {group,release_single}
+%% Subgroup of {group,release}, contains all cases that are not
+%% related to global_group
+init_per_group(release_single, Config) ->
+ Dog = ?t:timetrap(?default_timeout),
+
+ %% Create some more releases to upgrade to
+ ok = create_p1i(Config),
+ ok = create_p2a(Config),
+
+ ?t:timetrap_cancel(Dog);
+
+%% {group,release_gg}
+%% Subgroup of {group,release}. global_group tests.
+init_per_group(release_gg, Config0) ->
+ Config = [{sname_prefix,release_gg}|Config0],
+
+ PrivDir = priv_dir(Config),
+ Dog = ?t:timetrap(?default_timeout),
+
+ reg_print_proc(), %% starts a printer process on this node
+
+ Snames = [Gg1Sname,Gg2Sname,Gg3Sname,Gg4Sname,Gg5Sname,Gg6Sname] =
+ gg_node_snames(Config),
+
+ %% kill all possible nodes which are to be used
+ ok = stop_nodes([node_name(Sname) || Sname <- Snames]),
+
+ %% For gg1, gg3, gg4 and gg5: create a target system running
+ %% P1G, and with P1H unpacked.
+ %% For gg2 and gg6: create a target system running P1H.
+ %% Use gg2 for unpacking and permanenting P1H.
+ ok = copy_installed(Config,p1g_install,[Gg2Sname]),
+ InstallNode = unpack_p1h(Config,Gg2Sname),
+ ok = copy_installed(Config,Gg2Sname,[Gg1Sname,Gg3Sname,Gg4Sname,Gg5Sname]),
+ ok = permanent_p1h(InstallNode),
+ ok = stop_nodes([InstallNode]),
+ ok = copy_installed(Config,Gg2Sname,[Gg6Sname]),
+
+ %% Replace the sys.config files
+ %% The reason for not creating the releases with these configs in
+ %% the first place (create_p1g, create_p1h) is that then the
+ %% InstallNode (gg2) will be very slow started since it will try
+ %% to synch with the other nodes in the global group.
+ %% Also, the rpc call for installing the P1H release (in
+ %% permanent_p1h/1) would return {rpc,nodedown} due to change of
+ %% global groups.
+ lists:foreach(
+ fun(Sname) ->
+ ReleasesDir = filename:join([PrivDir,Sname,"releases"]),
+ write_term_file(filename:join([ReleasesDir,"P1G","sys.config"]),
+ gg_config([Gg1Sname,Gg3Sname,Gg4Sname,Gg5Sname])),
+ write_term_file(filename:join([ReleasesDir,"P1H","sys.config"]),
+ gg_config([Gg1Sname,Gg2Sname,Gg4Sname,
+ Gg5Sname,Gg6Sname]))
+ end,
+ Snames),
+
+ ?t:timetrap_cancel(Dog),
+ [{snames,Snames}|Config].
+
+
+end_per_group(release, Config) ->
+ Dog = ?t:timetrap(?default_timeout),
+ stop_print_proc(),
+ delete_release(Config),
+ ?t:timetrap_cancel(Dog),
+ Config;
+end_per_group(_GroupName, Config) ->
+ Config.
+
+
+init_per_testcase(Case, Config0) ->
+ Dog = test_server:timetrap(?default_timeout),
+ Config = [{sname_prefix,Case},{watchdog, Dog}|Config0],
+ try apply(?MODULE,Case,[cleanup,Config])
+ catch error:undef -> ok
+ end,
+ ?t:format("~n======= init_per_testcase done =======~n",[]),
+ Config.
+
+end_per_testcase(Case, Config) ->
+ ?t:format("~n======= start end_per_testcase =======~n",[]),
+ Dog=?config(watchdog, Config),
+ test_server:timetrap_cancel(Dog),
+
+ %% DEBUG
+ case ?config(tc_status,Config) of
+ ok ->
+ ok;
+ _Fail ->
+ %% save logs from master and client nodes
+ PrivDir = priv_dir(Config),
+ SaveDir = filename:join(PrivDir,save),
+ FailDir = filename:join(SaveDir,lists:concat(["failed-",Case])),
+ ok = filelib:ensure_dir(filename:join(FailDir,"*")),
+
+ LogDirs = filelib:wildcard(filename:join([PrivDir,"*",log])),
+
+ lists:foreach(
+ fun(LogDir) ->
+ ["log",Sname|_] = lists:reverse(filename:split(LogDir)),
+ copy_tree(Config,LogDir,Sname,FailDir)
+ end,
+ LogDirs),
+
+ case filelib:is_file("sasl_erl_crash.dump") of
+ true ->
+ copy_file("sasl_erl_crash.dump",FailDir);
+ _ ->
+ ok
+ end
+
+ end,
+ %% End DEBUG
+
+ %% Remove any remaining sasl_erl_crash.dump
+ %% These can occur when a new master@<host> is started, before
+ %% the old usage of the name is unregistered, causing the node to
+ %% terminate. (This has no effect on the test case, as the node is
+ %% immediately restarted by heart and the test cases wait until
+ %% the node is actually up and running -- see wait_nodes_up/2)
+ file:delete("sasl_erl_crash.dump"),
+
+ try apply(?MODULE,Case,[cleanup,Config])
+ catch error:undef -> ok
+ end,
+ ok.
+
+gg_node_snames(Config) ->
+ [tc_sname(Config,X) || X <- [gg1,gg2,gg3,gg4,gg5,gg6]].
+
+
+%%%-----------------------------------------------------------------
+%%% TEST CASES
+
+
+%% Executed instead of release group when no run_erl program exists
+no_run_erl(Config) when is_list(Config) ->
+ {comment, "No run_erl program"}.
+
+
+
+%% Test upgrade and downgrade of erts
+upgrade(Conf) when is_list(Conf) ->
+ reg_print_proc(), %% starts a printer process on test_server node
+ ?t:format("upgrade ~p~n",[reg_print_proc]),
+ PrivDir = priv_dir(Conf),
+ Sname = tc_sname(Conf), % nodename for use in this testcase
+
+ %% Copy the P1G release to a directory for use in this testcase
+ ok = copy_installed(Conf,p1g_install,[Sname]),
+
+ %% start the test node
+ [TestNode] = start_nodes(Conf,[Sname],"upgrade start"),
+
+ %% unpack and install P1H
+ ok = rpc_inst(TestNode, install_1, [PrivDir]),
+ stop_cover(TestNode),
+ reboot_and_wait(TestNode,"install_1"),
+
+ %% reinstall P1H and make it permanent
+ ok = rpc_inst(TestNode, install_2, []),
+ stop_cover(TestNode),
+ reboot_and_wait(TestNode,"install_2",[a]),
+
+ %% check that P1H is permanent, unpack and install P1I, unpack and install P2A
+ TestNodeInit1 = rpc:call(TestNode,erlang,whereis,[init]),
+ ok = rpc_inst(TestNode, install_3, [PrivDir]),
+ stop_cover(TestNode),
+ ok = rpc_inst(TestNode, install_3a, []),
+ wait_nodes_up([{TestNode,TestNodeInit1}],"install_3",[a]),
+
+ %% check that P2A is used, reboot from P1I
+ ok = rpc_inst(TestNode, install_4, []),
+ stop_cover(TestNode),
+ reboot_and_wait(TestNode,"install_4",[a]),
+
+ %% check that P1I, reinstall P2A
+ TestNodeInit2 = rpc:call(TestNode,erlang,whereis,[init]),
+ ok = rpc_inst(TestNode, install_5, []),
+ stop_cover(TestNode),
+ ok = rpc_inst(TestNode, install_5a, []),
+ wait_nodes_up([{TestNode,TestNodeInit2}],"install_5",[a]),
+
+ %% check that P2A is used, make P2A permanent
+ ok = rpc_inst(TestNode, install_6, []),
+ stop_cover(TestNode),
+ reboot_and_wait(TestNode,"install_6",[a]),
+
+ %% check that P2A is permanent, install old P1H
+ TestNodeInit3 = rpc:call(TestNode,erlang,whereis,[init]),
+ stop_cover(TestNode),
+ ok = rpc_inst(TestNode, install_7, []),
+ wait_nodes_up([{TestNode,TestNodeInit3}],"install_7",[a]),
+
+ %% check that P1H is permanent, remove P1I and P2A
+ ok = rpc_inst(TestNode, install_8, []),
+ stop_cover(TestNode),
+ reboot_and_wait(TestNode,"install_8",[a]),
+
+ %% check that P1H is permanent, reboot old P1G
+ TestNodeInit4 = rpc:call(TestNode,erlang,whereis,[init]),
+ stop_cover(TestNode),
+ ok = rpc_inst(TestNode, install_9, []),
+ wait_nodes_up([{TestNode,TestNodeInit4}],"install_9"),
+
+ %% check that P1G is permanent, remove P1H
+ ok = rpc_inst(TestNode, install_10, []),
+ stop_cover(TestNode),
+ reboot_and_wait(TestNode,"install_10"),
+
+ %% check that P1G is permanent
+ ok = rpc_inst(TestNode, install_11, []),
+
+ ok.
+
+upgrade(cleanup,Config) ->
+ TestNode = tc_full_node_name(Config),
+ ok = stop_nodes([TestNode]).
+
+reboot_and_wait(Node,Tag) ->
+ reboot_and_wait(Node,Tag,[]).
+
+reboot_and_wait(Node,Tag,Apps) ->
+ InitPid = rpc:call(Node,erlang,whereis,[init]),
+ ok = rpc:call(Node,init,reboot,[]),
+ wait_nodes_up([{Node,InitPid}],Tag,Apps).
+
+
+%% Test upgrade and downgrade of erts, diskless
+client1(Conf) when is_list(Conf) ->
+ reg_print_proc(), %% starts a printer process on test_server node
+ PrivDir = priv_dir(Conf),
+ Master = tc_sname(Conf,master),
+ Client = tc_sname(Conf,client),
+ MasterDir = filename:join(PrivDir,Master),
+
+ %% Copy the P1G release to a directory for use in this testcase
+ ok = copy_installed(Conf,p1g_install,[Master]),
+ ok = copy_client(Conf,Master,Client,"start_cli1"),
+
+ %% start the master node
+ [TestNode] = start_nodes(Conf,[Master],"client1"),
+
+ ok = rpc_inst(TestNode, client1_1, [PrivDir,MasterDir,Client]),
+
+ ok.
+
+client1(cleanup,Config) ->
+ MasterNode = tc_full_node_name(Config,master),
+ ClientNode = tc_full_node_name(Config,client),
+ ok = stop_nodes([MasterNode,ClientNode]).
+
+
+
+%% Test diskless release handling when illegal master node
+client2(Conf) when is_list(Conf) ->
+ reg_print_proc(), %% starts a printer process on test_server node
+ PrivDir = priv_dir(Conf),
+ Master = tc_sname(Conf,master),
+ Client = tc_sname(Conf,client),
+
+ %% Copy the P1G release to a directory for use in this testcase
+ ok = copy_installed(Conf,p1g_install,[Master]),
+ ok = copy_client(Conf,Master,Client,"start_cli2"),
+
+ %% start the master node
+ [TestNode] = start_nodes(Conf,[Master],"client2"),
+
+ ok = rpc_inst(TestNode, client2, [PrivDir,Client]),
+
+ ok.
+
+client2(cleanup,Config) ->
+ MasterNode = tc_full_node_name(Config,master),
+ ClientNode = tc_full_node_name(Config,client),
+ ok = stop_nodes([MasterNode,ClientNode]).
+
+
+
+%% Test instructions _not_ tested by the installer module.
+instructions(Conf) when is_list(Conf) ->
+ DataDir = ?config(data_dir, Conf),
+
+ Dir = filename:join(DataDir, "c"),
+ true = code:add_path(Dir),
+ check_bstate("no", []),
+ ok = application:start(c),
+ ok = wait_for(bb),
+ check_bstate("first", []),
+ FirstBB = whereis(bb),
+
+ case whereis(cc) of
+ Pid when is_pid(Pid) -> ok;
+ _ -> ?t:fail("cc not started")
+ end,
+
+ %% Stop and start cc process
+ S1 = [point_of_no_return,
+ {stop, [aa]},
+ {apply, {?MODULE, no_cc, []}},
+ {start, [aa]}],
+ {ok, _} = release_handler_1:eval_script(S1, [], []),
+
+ case whereis(cc) of
+ Pid2 when is_pid(Pid2) -> ok;
+ _ -> ?t:fail("cc not started")
+ end,
+
+ %% Make bb run old version of b.
+ S2 = [point_of_no_return,
+ {remove, {b, soft_purge, soft_purge}}],
+ {ok, [{b, soft_purge}]} = release_handler_1:eval_script(S2, [], []),
+ check_bstate("first", [FirstBB]),
+
+ false = code:is_loaded(b),
+ {error,{old_processes,b}} = release_handler_1:eval_script(S2,[],[]),
+ check_bstate("first", [FirstBB]),
+
+ %% Let supervisor restart bb with new code
+ S3 = [point_of_no_return,
+ {purge, [b]}],
+ {ok, []} = release_handler_1:eval_script(S3, [], []),
+ ok = wait_for(bb),
+ check_bstate("second", []),
+ SecondBB = whereis(bb),
+
+ if
+ SecondBB =:= FirstBB -> ?t:fail("bb not killed");
+ true -> ok
+ end,
+
+ %% Restart bb yet another time
+ ok = application:stop(c),
+ ok = application:start(c),
+ ok = wait_for(bb),
+ check_bstate("third", []),
+ ThirdBB = whereis(bb),
+
+ case ThirdBB of
+ _ when is_pid(ThirdBB) -> ok;
+ undefined -> ?t:fail("bb not started")
+ end,
+
+ %% Make bb run old version of b.
+ %%c:l(b),
+ check_bstate("third", []),
+ false = code:purge(b),
+ check_bstate("third", []),
+ {module,b} = code:load_file(b),
+ check_bstate("third", [ThirdBB]),
+
+ %% Let supervisor restart bb yet another time
+ S4 = [point_of_no_return,
+ {remove, {b, brutal_purge, soft_purge}}],
+ {ok, HopefullyEmpty} = release_handler_1:eval_script(S4, [], []),
+ ok = wait_for(bb),
+ FourthBB = whereis(bb),
+
+ case HopefullyEmpty of
+ [{b, soft_purge}] ->
+ %% The process managed to start between purge and delete
+ check_bstate("fourth", [FourthBB]);
+ [] ->
+ %% The process started after delete
+ check_bstate("fourth", [])
+ end,
+
+ application:stop(c),
+ check_bstate("no", []),
+ ok.
+
+instructions(cleanup,Conf) ->
+ application:stop(c),
+ really_del_code([aa,b,c_sup]),
+ code:del_path(filename:join(?config(data_dir,Conf), "c")),
+ ok.
+
+really_del_code(Mods) ->
+ lists:foreach(fun(Mod) ->
+ code:purge(Mod), % remove old code
+ code:delete(Mod),% make current code old
+ code:purge(Mod) % remove old code
+ end,
+ Mods).
+
+check_bstate(Slogan,ExpectedProcs) ->
+ BB = whereis(bb),
+ ActualProcs = lists:sort([P || P <- processes(),
+ erlang:check_process_code(P, b)]),
+ ExpectedProcs2 = lists:sort(ExpectedProcs),
+ ?t:format("check_bstate:~n~p~n~p~n",
+ [{"bb process", Slogan, BB},
+ {"Processes running old b code", ActualProcs}]),
+ if
+ Slogan =:= "no", BB =/= undefined ->
+ ?t:fail("instructions failed; process bb is running");
+ Slogan =/= "no", BB =:= undefined ->
+ ?t:fail("instructions failed; process bb is not running");
+ ExpectedProcs2 =:= [], ActualProcs =/= ExpectedProcs2 ->
+ ?t:fail("instructions failed; old b processes are running");
+ ActualProcs =/= ExpectedProcs2 ->
+ ?t:fail("instructions failed; wrong number of old b processes are running");
+ true ->
+ ok
+ end.
+
+wait_for(Name) ->
+ case whereis(Name) of
+ undefined ->
+ timer:sleep(100),
+ wait_for(Name);
+ Pid when is_pid(Pid) ->
+ ok
+ end.
+
+no_cc() ->
+ case whereis(cc) of
+ Pid when is_pid(Pid) -> ?t:fail("cc not stopped");
+ _ -> ok
+ end.
+
+
+
+%%%-----------------------------------------------------------------
+%%% Testing of reported bugs and other tickets.
+%%%-----------------------------------------------------------------
+
+%%-----------------------------------------------------------------
+%% Ticket: OTP-2740
+%% Slogan: vsn not numeric doesn't work so good in release_handling
+%%-----------------------------------------------------------------
+%% Test vsn.
+otp_2740(Conf) ->
+ DataDir = ?config(data_dir, Conf),
+ Dir = filename:join(DataDir, "otp_2740"),
+ true = code:add_path(Dir),
+
+ {module, vsn_numeric} = c:l(vsn_numeric),
+ {module, vsn_tuple} = c:l(vsn_tuple),
+ {module, vsn_list} = c:l(vsn_list),
+ {module, vsn_atom} = c:l(vsn_atom),
+ {module, vsn_string} = c:l(vsn_string),
+
+ 231894 = release_handler_1:get_current_vsn(vsn_numeric),
+ {tuple,["of",terms]} = release_handler_1:get_current_vsn(vsn_tuple),
+ [list,"of",{some,terms}] = release_handler_1:get_current_vsn(vsn_list),
+ atom = release_handler_1:get_current_vsn(vsn_atom),
+ "a string" = release_handler_1:get_current_vsn(vsn_string),
+
+ true = code:del_path(Dir),
+ ok.
+
+%%-----------------------------------------------------------------
+%% Ticket: OTP-2760
+%% Slogan: when an application is removed from a node it is not unloaded
+%%-----------------------------------------------------------------
+%% Test that when an application is removed from a node it is also unloaded.
+otp_2760(Conf) ->
+ PrivDir = priv_dir(Conf),
+ Dir = filename:join(PrivDir,"otp_2760"),
+ DataDir = ?config(data_dir,Conf),
+ LibDir = filename:join([DataDir,app1_app2,lib1]),
+
+ Rel1 = create_and_install_fake_first_release(Dir,[{app1,"1.0",LibDir}]),
+ Rel2 = create_fake_upgrade_release(Dir,"after",[],{Rel1,Rel1,[LibDir]}),
+ Rel2Dir = filename:dirname(Rel2),
+
+ %% Start a node with Rel1.boot and check that the app1 module is loaded
+ {ok, Node} = t_start_node(otp_2760, Rel1, []),
+ {file, _} = rpc:call(Node, code, is_loaded, [app1]),
+
+ %% Execute the relup script and check that app1 is unloaded
+ {ok, [{"after", [{_Rel1Vsn, _Descr, Script}], _}]} =
+ file:consult(filename:join(Rel2Dir, "relup")),
+ {ok, []} = rpc:call(Node, release_handler_1, eval_script,
+ [Script, [], []]),
+ false = rpc:call(Node, code, is_loaded, [app1]),
+
+ true = stop_node(Node),
+ ok.
+
+%% Test upgrade using other filesystem than the defined in OTP and
+%% option {update_paths, true}
+otp_5761(Conf) when is_list(Conf) ->
+
+ %% In the following test case, the release upgrade is somewhat
+ %% simplified (since it is not this procedure in itself we want to
+ %% test, but that application code directories are set correctly.)
+ %% Existing Erlang release is used as base, instead of creating
+ %% a new one.
+
+ %% Set some paths
+ PrivDir = priv_dir(Conf),
+ Dir = filename:join(PrivDir,"otp_5761"),
+ RelDir = filename:join(?config(data_dir, Conf), "app1_app2"),
+ LibDir1 = filename:join(RelDir, "lib1"),
+ LibDir2 = filename:join(RelDir, "lib2"),
+
+ %% Create the releases
+ Rel1 = create_and_install_fake_first_release(Dir,
+ [{app1,"1.0",LibDir1},
+ {app2,"1.0",LibDir1}]),
+ Rel2 = create_fake_upgrade_release(Dir,
+ "2",
+ [{app1,"2.0",LibDir2},
+ {app2,"1.0",LibDir2}],
+ {Rel1,Rel1,[LibDir1]}),
+ Rel1Dir = filename:dirname(Rel1),
+ Rel2Dir = filename:dirname(Rel2),
+
+ %% Start a slave node
+ {ok, Node} = t_start_node(otp_5761, Rel1, filename:join(Rel1Dir,"sys.config")),
+
+ %% Bind some variable names that will be used in patternmatching below
+ App11Dir = filename:join([LibDir1, "app1-1.0"]),
+ App12Dir = filename:join([LibDir2, "app1-2.0"]),
+ App2aDir = filename:join([LibDir1, "app2-1.0"]),
+ App2bDir = filename:join([LibDir2, "app2-1.0"]),
+
+ %% Make sure correct code paths are used
+ App11Dir = rpc:call(Node, code, lib_dir, [app1]),
+ App2aDir = rpc:call(Node, code, lib_dir, [app2]),
+
+ %% Unpack rel2 (make sure it does not work if an AppDir is bad)
+ LibDir3 = filename:join(RelDir, "lib3"),
+ {error, {no_such_directory, _}} =
+ rpc:call(Node, release_handler, set_unpacked,
+ [Rel2++".rel", [{app1,"2.0",LibDir2}, {app2,"1.0",LibDir3}]]),
+ {ok, RelVsn2} =
+ rpc:call(Node, release_handler, set_unpacked,
+ [Rel2++".rel", [{app1,"2.0",LibDir2}, {app2,"1.0",LibDir2}]]),
+ ok = rpc:call(Node, release_handler, install_file,
+ [RelVsn2, filename:join(Rel2Dir, "relup")]),
+ ok = rpc:call(Node, release_handler, install_file,
+ [RelVsn2, filename:join(Rel2Dir, "start.boot")]),
+ ok = rpc:call(Node, release_handler, install_file,
+ [RelVsn2, filename:join(Rel2Dir, "sys.config")]),
+
+ %% Install RelVsn2 without {update_paths, true} option
+ {ok, RelVsn1, []} =
+ rpc:call(Node, release_handler, install_release, [RelVsn2]),
+ App12Dir = rpc:call(Node, code, lib_dir, [app1]),
+ App2aDir = rpc:call(Node, code, lib_dir, [app2]),
+
+ %% Install RelVsn1 again
+ {ok, RelVsn1, []} =
+ rpc:call(Node, release_handler, install_release, [RelVsn1]),
+
+ %% Install RelVsn2 with {update_paths, true} option
+ {ok, RelVsn1, []} =
+ rpc:call(Node, release_handler, install_release,
+ [RelVsn2, [{update_paths, true}]]),
+ App12Dir = rpc:call(Node, code, lib_dir, [app1]),
+ App2bDir = rpc:call(Node, code, lib_dir, [app2]),
+
+ %% Install RelVsn1 again
+ {ok, RelVsn1, []} =
+ rpc:call(Node, release_handler, install_release,
+ [RelVsn1, [{update_paths, true}]]),
+ App11Dir = rpc:call(Node, code, lib_dir, [app1]),
+ App2aDir = rpc:call(Node, code, lib_dir, [app2]),
+
+ %% Stop the slave node
+ true = stop_node(Node),
+ ok.
+
+%% Test upgrade and downgrade of applications
+eval_appup(Conf) when is_list(Conf) ->
+
+ %% OTP-6162
+ %% Create an ETS table which is updated by app1 if there is any
+ %% change made to the application configuration parameter 'var'
+ %% (see config_change/3 in myrel/lib1|2/app1-1|2.0/src/app1.erl)
+ ets:new(otp_6162, [set, public, named_table]),
+
+ %% Set some paths
+ RelDir = filename:join(?config(data_dir, Conf), "app1_app2"),
+ App11Dir = filename:join([RelDir, "lib1", "app1-1.0"]),
+ App12Dir = filename:join([RelDir, "lib2", "app1-2.0"]),
+ EbinDir = filename:join(App11Dir, "ebin"),
+
+ %% Start app1-1.0
+ code:add_patha(EbinDir),
+ ok = application:start(app1),
+ App11Dir = code:lib_dir(app1),
+ ok = gen_server:call(harry, error),
+
+ %% Upgrade to app1-2.0
+ {ok, []} = release_handler:upgrade_app(app1, App12Dir),
+ App12Dir = code:lib_dir(app1),
+ error = gen_server:call(harry, error),
+
+ %% OTP-6162
+ %% Value of config parameter 'var' should now be 'val2'
+ %% (see myrel/lib2/app1-2.0/ebin/app1.app)
+ [{var,val2}] = ets:lookup(otp_6162, var),
+
+ %% Downgrade to app1-1.0
+ {ok, []} = release_handler:downgrade_app(app1,"1.0",App11Dir),
+ App11Dir = code:lib_dir(app1),
+ ok = gen_server:call(harry, error),
+
+ %% OTP-6162
+ %% Value of config parameter 'var' should now be 'val1'
+ %% (see myrel/lib1/app1-1.0/ebin/app1.app)
+ [{var,val1}] = ets:lookup(otp_6162, var),
+
+ ok = application:stop(app1),
+ ok = application:unload(app1),
+
+ true = code:del_path(EbinDir),
+ ok.
+
+
+%% Test the example/target_system.erl module
+target_system(Conf) when is_list(Conf) ->
+ PrivDir = priv_dir(Conf),
+ DataDir = ?config(data_dir,Conf),
+
+ TargetCreateDir = filename:join([PrivDir,"target_system","create"]),
+ TargetInstallDir = filename:join([PrivDir,"target_system","install"]),
+
+ ok = filelib:ensure_dir(filename:join(TargetCreateDir,"xx")),
+ ok = filelib:ensure_dir(filename:join(TargetInstallDir,"xx")),
+
+
+ %% Create the .rel file
+ ErtsVsn = erlang:system_info(version),
+ RelName = filename:join(TargetCreateDir,"ts-1.0"),
+ RelFile = RelName++".rel",
+ RelVsn = "R1A",
+ create_rel_file(RelFile,RelName,RelVsn,ErtsVsn,[{a, "1.0"}]),
+
+ %% Build the target_system module
+ ExamplesEbin = filename:join([code:lib_dir(sasl),examples,ebin]),
+ TSPath =
+ case filelib:is_file(filename:join(ExamplesEbin,"target_system.beam")) of
+ true ->
+ ExamplesEbin;
+ false ->
+ {ok,_} =
+ compile:file(filename:join(DataDir,"target_system.erl"),
+ [{outdir,TargetCreateDir}]),
+ TargetCreateDir
+ end,
+ code:add_path(TSPath),
+
+ %% Create the release
+ target_system:create(RelName,[{path,[filename:join([DataDir,
+ lib,
+ "a-1.0",
+ ebin])]}]),
+
+ %% Install the release
+ target_system:install(RelName,TargetInstallDir),
+
+ code:del_path(TSPath),
+
+ %% Check that all files exist in installation
+ true = filelib:is_dir(filename:join(TargetInstallDir,"erts-"++ErtsVsn)),
+ LibDir = filename:join(TargetInstallDir,lib),
+ {ok,KernelVsn} = application:get_key(kernel,vsn),
+ {ok,StdlibVsn} = application:get_key(stdlib,vsn),
+ {ok,SaslVsn} = application:get_key(sasl,vsn),
+ true = filelib:is_dir(filename:join(LibDir,"kernel-"++KernelVsn)),
+ true = filelib:is_dir(filename:join(LibDir,"stdlib-"++StdlibVsn)),
+ true = filelib:is_dir(filename:join(LibDir,"sasl-"++SaslVsn)),
+ true = filelib:is_dir(filename:join(LibDir,"a-1.0")),
+ RelDir = filename:join(TargetInstallDir,releases),
+ true = filelib:is_regular(filename:join(RelDir,"RELEASES")),
+ true = filelib:is_regular(filename:join(RelDir,"start_erl.data")),
+ true = filelib:is_regular(filename:join(RelDir,
+ filename:basename(RelFile))),
+ true = filelib:is_dir(filename:join(RelDir,RelVsn)),
+ true = filelib:is_regular(filename:join([RelDir,RelVsn,"start.boot"])),
+ BinDir = filename:join(TargetInstallDir,bin),
+ true = filelib:is_regular(filename:join(BinDir,"start.boot")),
+ true = filelib:is_regular(filename:join(BinDir,erl)),
+ true = filelib:is_regular(filename:join(BinDir,start_erl)),
+ true = filelib:is_regular(filename:join(BinDir,start)),
+ true = filelib:is_regular(filename:join(BinDir,epmd)),
+ true = filelib:is_regular(filename:join(BinDir,run_erl)),
+ true = filelib:is_regular(filename:join(BinDir,to_erl)),
+
+ %% Check content of files
+ {ok,SED} = file:read_file(filename:join(RelDir,"start_erl.data")),
+ [ErtsVsn,RelVsn] = string:tokens(binary_to_list(SED),"\s\n"),
+ ok.
+
+
+
+%%%=================================================================
+%%% Testing global groups.
+%%%=================================================================
+
+%% This test case involves P1G and P1H with the sys.config as
+%% specified in gg_config/1. The test case checks that the global
+%% group information is correct before and after the upgrade and also
+%% after terminating one of the nodes. The flow is as follows:
+%% 1. Start all four nodes of global group gg1 with P1G
+%% 2. Terminate one of the nodes, and upgrade the others to P1H. P1H
+%% config adds to more nodes to the global group.
+%% 3. Start the two remaining nodes with P1H
+upgrade_gg(Conf) ->
+ [Gg1Sname,Gg2Sname,Gg3Sname,Gg4Sname,Gg5Sname,Gg6Sname] =
+ ?config(snames,Conf),
+
+ %% start gg1, gg3, gg4, gg5 and check that global group info is ok
+ Nodes1 = [Gg1,Gg3,Gg4,Gg5] =
+ start_nodes(Conf,[Gg1Sname,Gg3Sname,Gg4Sname,Gg5Sname],"upgrade_gg"),
+
+ %% Give some time to synch nodes, then check global group info.
+ timer:sleep(1000),
+ [check_gg_info(Node,Nodes1,[],Nodes1--[Node]) || Node <- Nodes1],
+
+ %% register a process on each of the nodes
+ ok = rpc:call(Gg1, installer, reg_proc, [reg1]),
+ ok = rpc:call(Gg3, installer, reg_proc, [reg3]),
+ ok = rpc:call(Gg4, installer, reg_proc, [reg4]),
+ ok = rpc:call(Gg5, installer, reg_proc, [reg5]),
+ are_names_reg_gg(Gg1, [reg1, reg3, reg4, reg5]),
+
+ %% Stop gg3, then upgrade gg1, gg4 and gg5 to P1H
+ ok = stop_nodes([Gg3]),
+
+ ok = install_release_changed_gg(Gg1,"P1H"),
+ ok = install_release_changed_gg(Gg4,"P1H"),
+ ok = install_release_changed_gg(Gg5,"P1H"),
+
+ %% Check global group info
+ Gg2 = node_name(Gg2Sname),
+ Gg6 = node_name(Gg6Sname),
+ Nodes2 = [Gg1,Gg4,Gg5],
+ [check_gg_info(Node,Nodes2,[Gg2,Gg6],Nodes2--[Node]) || Node <- Nodes2],
+
+ %% start gg2 and gg6
+ [Gg2,Gg6] = start_nodes(Conf,[Gg2Sname,Gg6Sname],"upgrade_gg start gg2/gg6"),
+
+ %% reg proc on each of the nodes
+ ok = rpc:call(Gg2, installer, reg_proc, [reg2]),
+ ok = rpc:call(Gg6, installer, reg_proc, [reg6]),
+ are_names_reg_gg(Gg1, [reg1, reg2, reg4, reg5, reg6]),
+
+ %% Check global group info
+ Nodes3 = [Gg1,Gg2,Gg4,Gg5,Gg6],
+ [check_gg_info(Node,Nodes3,[],Nodes3--[Node]) || Node <- Nodes3],
+
+ ok.
+
+upgrade_gg(cleanup,Config) ->
+ Snames = ?config(snames,Config),
+ NodeNames = [node_name(Sname) || Sname <- Snames],
+ ok = stop_nodes(NodeNames).
+
+
+
+
+%%%=================================================================
+%%% Misceleaneous functions
+%%%=================================================================
+stop_nodes(Nodes) ->
+ ?t:format("Stopping nodes: ~p~n",[Nodes]),
+ Running =
+ lists:foldl(fun(Node,Acc) ->
+ Now = now(),
+ stop_cover(Node),
+ case rpc:call(Node,installer,stop,[Now]) of
+ {badrpc,nodedown} ->
+ Acc;
+ Other ->
+ ?t:format("Stop ~p(~p): ~p~n",
+ [Node,Now,Other]),
+ [Node|Acc]
+ end
+ end, [], Nodes),
+ wait_nodes_down(Running).
+
+
+wait_nodes_down(Nodes) ->
+ ?t:format( "wait_nodes_down ~p:",[Nodes]),
+ wait_nodes_down(Nodes, 30).
+
+wait_nodes_down(Nodes, 0) ->
+ test_server:fail({error, {"could not kill nodes", Nodes}});
+wait_nodes_down(Nodes, N) ->
+ Fun = fun(Node, A) ->
+ case net_adm:ping(Node) of
+ pong ->
+ ?t:format( " net_adm:ping(~p) = pong", [Node]),
+ [Node|A];
+ pang ->
+ ?t:format( " net_adm:ping(~p) = pang", [Node]),
+ A
+ end
+ end,
+ Pang = lists:foldl(Fun, [], Nodes),
+ case Pang of
+ [] ->
+ ?t:format("",[]),
+ ok;
+ _ ->
+ timer:sleep(1000),
+ wait_nodes_down(Pang, N-1)
+ end.
+
+
+
+wait_nodes_up(Nodes, Tag) ->
+ wait_nodes_up(Nodes, Tag, []).
+
+wait_nodes_up(Nodes0, Tag, Apps) ->
+ ?t:format("wait_nodes_up(~p, ~p, ~p):",[Nodes0, Tag, Apps]),
+ Nodes = fix_nodes(Nodes0),
+ wait_nodes_up(Nodes, Tag, lists:umerge(Apps,[kernel,stdlib,sasl]), 30).
+
+fix_nodes([{Node,InitPid}|Nodes]) ->
+ [{Node,InitPid} | fix_nodes(Nodes)];
+fix_nodes([Node|Nodes]) ->
+ [{Node,fake_init_pid} | fix_nodes(Nodes)];
+fix_nodes([]) ->
+ [].
+
+wait_nodes_up(Nodes, Tag, Apps, 0) ->
+ test_server:fail({error, {"nodes not started", Nodes, Tag, Apps}});
+wait_nodes_up(Nodes, Tag, Apps, N) ->
+ Fun =
+ fun(NodeInfo={Node,OldInitPid}, A) ->
+ case rpc:call(Node, application, which_applications, []) of
+ {badrpc, nodedown} ->
+ ?t:format( " ~p = {badarg, nodedown}",[Node]),
+ [NodeInfo | A];
+ List when is_list(List)->
+ ?t:format( " ~p = [~p]",[Node, List]),
+ case lists:all(fun(App) ->
+ lists:keymember(App,1,List)
+ end, Apps) of
+ true ->
+ case rpc:call(Node,erlang,whereis,[init]) of
+ OldInitPid ->
+ [NodeInfo | A];
+ _ ->
+ start_cover(Node),
+ A
+ end;
+ false ->
+ [NodeInfo | A]
+ end
+ end
+ end,
+ Pang = lists:foldl(Fun,[],Nodes),
+ case Pang of
+ [] ->
+ ?t:format("",[]),
+ ok;
+ _ ->
+ timer:sleep(1000),
+ wait_nodes_up(Pang, Tag, Apps, N-1)
+ end.
+
+
+
+
+are_names_reg_gg(Node, Names) ->
+ ?t:format( "are_names_reg_gg ~p~n",[Names]),
+ are_names_reg_gg(Node, Names, 30).
+
+are_names_reg_gg(Node, Names, N) ->
+ case lists:sort(rpc:call(Node, global, registered_names, [])) of
+ Names ->
+ ok;
+ Regs when N > 0 ->
+ timer:sleep(1000),
+ ?t:format( "are_names_reg_gg Regs ~p~n",[Regs]),
+ are_names_reg_gg(Node, Names, N-1);
+ Regs ->
+ ?t:fail({error, {"Names not registered",
+ {{"should :", Names},
+ {"was :", Regs}}}})
+ end.
+
+
+
+t_start_node(Name, Boot, SysConfig) ->
+ Args =
+ case Boot of
+ [] -> [];
+ _ -> " -boot " ++ Boot
+ end ++
+ case SysConfig of
+ [] -> [];
+ _ -> " -config " ++ SysConfig
+ end,
+ test_server:start_node(Name, slave, [{args, Args}]).
+
+stop_node(Node) ->
+ ?t:stop_node(Node).
+
+
+copy_client(Conf,Master,Sname,StartScript) ->
+ io:format("copy_client(Conf)"),
+
+ DataDir = ?config(data_dir, Conf),
+ MasterDir = filename:join(priv_dir(Conf),Master),
+
+ {ok,Host} = inet:gethostname(),
+ {ok,IpTuple} = inet:getaddr(Host,inet),
+ IpAddr = inet_parse:ntoa(IpTuple),
+
+ CliNode = node_name(Sname),
+
+ Cli = filename:join([MasterDir, "clients", "type1", CliNode]),
+ ok = filelib:ensure_dir(filename:join([Cli,"bin","."])),
+ ok = filelib:ensure_dir(filename:join([Cli,"releases","."])),
+ ok = filelib:ensure_dir(filename:join([Cli,"log","."])),
+
+ P1GOrig = filename:join([MasterDir, "releases", "P1G"]),
+ ok = copy_tree(Conf,P1GOrig,filename:join(Cli,"releases")),
+
+ ok = subst_file(filename:join([DataDir, "clients", StartScript]),
+ filename:join([Cli,"bin","start"]),
+ [{"ROOT",MasterDir},
+ {"MASTER",atom_to_list(Master)},
+ {"IPADDR",IpAddr}],
+ [{chmod,8#0755}]),
+
+ StartErlData = filename:join([MasterDir, "releases", "start_erl.data"]),
+ CliRelDir = filename:join([Cli, "releases"]),
+ copy_file(StartErlData, CliRelDir),
+
+ RR = filename:join([MasterDir, "releases", "RELEASES"]),
+ copy_file(RR, CliRelDir),
+
+ ok.
+
+
+delete_release(Conf) ->
+ PrivDir = priv_dir(Conf),
+
+ {ok, OrigWd} = file:get_cwd(),
+
+ ok = file:set_cwd(PrivDir),
+ ?t:format("======== current dir ~p~n",[PrivDir]),
+ {ok, Dirs} = file:list_dir(PrivDir),
+ ?t:format("======== deleting ~p~n",[Dirs]),
+
+ ok = delete_release_os(Dirs),
+ ?t:format("======== remaining ~p~n",[file:list_dir(PrivDir)]),
+ ok = file:set_cwd(OrigWd),
+ ok.
+
+
+delete_release_os(Dirs) ->
+ case os:type() of
+ {unix, _} ->
+ delete_release_unix(Dirs);
+ {win32, _} ->
+ delete_release_win32(Dirs);
+ Os ->
+ test_server:fail({error, {not_yet_implemented_os, Os}})
+ end.
+
+
+delete_release_unix([]) ->
+ ok;
+delete_release_unix(["save"|Dirs]) ->
+ delete_release_unix(Dirs);
+delete_release_unix([Dir|Dirs]) ->
+ Rm = string:concat("rm -rf ", Dir),
+ ?t:format("============== COMMAND ~p~n",[Rm]),
+ case file:list_dir(Dir) of
+ {error, enotdir} ->
+ ok;
+ X ->
+ ?t:format("------- Dir ~p~n ~p~n",[Dir, X])
+ end,
+ case os:cmd(Rm) of
+ [] ->
+ ?t:format("------- Result of COMMAND ~p~n",[ok]);
+ Y ->
+ ?t:format("!!!!!!! delete ERROR Dir ~p Error ~p~n",[Dir, Y]),
+ ?t:format("------- ls -al ~p~n",[os:cmd("ls -al " ++ Dir)])
+ end,
+
+ delete_release_unix(Dirs).
+
+delete_release_win32([]) ->
+ ok;
+delete_release_win32(["save"|Dirs]) ->
+ delete_release_win32(Dirs);
+delete_release_win32([Dir|Dirs]) ->
+ Rm = string:concat("rmdir /s ", Dir),
+ [] = os:cmd(Rm),
+ delete_release_win32(Dirs).
+
+
+node_name(Sname) when is_atom(Sname) ->
+ {ok,Host} = inet:gethostname(),
+ list_to_atom(atom_to_list(Sname) ++ "@" ++ Host).
+
+copy_file(Src, Dest) ->
+ copy_file(Src, Dest, []).
+copy_file(Src, Dest, Opts) ->
+ case file:copy(Src,Dest) of
+ {ok,_} ->
+ preserve(Src,Dest,Opts),
+ chmod(Dest,Opts),
+ ok;
+ {error,eisdir} ->
+ NewDest = filename:join(Dest, filename:basename(Src)),
+ case file:copy(Src,NewDest) of
+ {ok,_} ->
+ preserve(Src,NewDest,Opts),
+ chmod(NewDest,Opts);
+ {error,Reason} ->
+ copy_error(Src,Dest,Reason)
+ end;
+ {error,Reason} ->
+ copy_error(Src,Dest,Reason)
+ end.
+
+preserve(Src,Dest,Opts) ->
+ case lists:member(preserve, Opts) of
+ true ->
+ {ok, FileInfo} = file:read_file_info(Src),
+ ok = file:write_file_info(Dest, FileInfo);
+ false ->
+ ok
+ end.
+
+chmod(Dest,Opts) ->
+ case lists:keyfind(chmod,1,Opts) of
+ {chmod,Mode} ->
+ ok = file:change_mode(Dest, Mode);
+ false ->
+ ok
+ end.
+
+
+
+copy_error(Src, Dest, Reason) ->
+ io:format("Copy ~s to ~s failed: ~s\n",
+ [Src,Dest,file:format_error(Reason)]),
+ ?t:fail(file_copy_failed).
+
+copy_tree(Conf, Src, DestDir) ->
+ case catch copy_tree(Conf, Src, filename:basename(Src), DestDir) of
+ ok ->
+ ok;
+ {'EXIT', {{badmatch,Error},_Stack}} ->
+ %% Most probably, an erl_tar call has failed.
+ %% Known to happen on some platforms (symbolic_link_too_long)
+ Error;
+ {'EXIT', Reason} ->
+ {error, Reason}
+ end.
+
+copy_tree(Conf, Src, NewName, DestDir) ->
+ PrivDir = priv_dir(Conf),
+ TempTarName = filename:join(PrivDir, "temp_tar_file.tar"),
+ %% Not compressing tar file here since that would increase test
+ %% suite time by almost 100%, and the tar file is deleted
+ %% imediately anyway.
+ {ok,Tar} = erl_tar:open(TempTarName, [write]),
+ ok = erl_tar:add(Tar, Src, NewName, []),
+ ok = erl_tar:close(Tar),
+ ok = erl_tar:extract(TempTarName, [{cwd,DestDir}]),
+ ok = file:delete(TempTarName),
+ ok.
+
+%% subst_file(Src, Dest, Vars)
+%% Src = Dest = string(), filename and path
+%% Vars = [{Var, Val}]
+%% Var = Val = string()
+%% Substitute all occurrences of %Var% for Val in Src, using the list
+%% of variables in Vars. Result is written to Dest.
+%%
+subst_file(Src, Dest, Vars) ->
+ subst_file(Src, Dest, Vars, []).
+subst_file(Src, Dest, Vars, Opts) ->
+ {ok, Bin} = file:read_file(Src),
+ Conts = binary_to_list(Bin),
+ NConts = subst(Conts, Vars),
+ ok = file:write_file(Dest, NConts),
+ preserve(Src,Dest,Opts),
+ chmod(Dest,Opts).
+
+subst(Str, Vars) ->
+ subst(Str, Vars, []).
+
+subst([$%, C| Rest], Vars, Result) when $A =< C, C =< $Z ->
+ subst_var([C| Rest], Vars, Result, []);
+subst([$%, C| Rest], Vars, Result) when $a =< C, C =< $z ->
+ subst_var([C| Rest], Vars, Result, []);
+subst([$%, C| Rest], Vars, Result) when C == $_ ->
+ subst_var([C| Rest], Vars, Result, []);
+subst([C| Rest], Vars, Result) ->
+ subst(Rest, Vars, [C| Result]);
+subst([], _Vars, Result) ->
+ lists:reverse(Result).
+
+subst_var([$%| Rest], Vars, Result, VarAcc) ->
+ Key = lists:reverse(VarAcc),
+ case lists:keysearch(Key, 1, Vars) of
+ {value, {Key, Value}} ->
+ subst(Rest, Vars, lists:reverse(Value, Result));
+ false ->
+ subst(Rest, Vars, [$%| VarAcc ++ [$%| Result]])
+ end;
+subst_var([C| Rest], Vars, Result, VarAcc) ->
+ subst_var(Rest, Vars, Result, [C| VarAcc]);
+subst_var([], Vars, Result, VarAcc) ->
+ subst([], Vars, [VarAcc ++ [$%| Result]]).
+
+
+priv_dir(Conf) ->
+ filename:absname(?config(priv_dir, Conf)). % Get rid of trailing slash
+
+latest_version(Dir) ->
+ List = filelib:wildcard(Dir ++ "*"),
+ lists:last(lists:sort(List)).
+
+%% A printer process which receives messages from other nodes and
+%% prints in the log
+reg_print_proc() ->
+ catch unregister(rh_print),
+ Pid = spawn_link(?MODULE, rh_print, []),
+ register(rh_print, Pid),
+ ok.
+
+rh_print() ->
+ receive
+ {print, {Module,Line}, [H|T]} ->
+ ?t:format("=== ~p:~p - ~p",[Module,Line,H]),
+ lists:foreach(fun(Term) -> ?t:format(" ~p",[Term]) end, T),
+ ?t:format("",[]),
+ rh_print();
+ kill ->
+ exit(normal)
+ end.
+
+stop_print_proc() ->
+ case whereis(rh_print) of %%removes the printer process
+ undefined ->
+ ok;
+ Pid when is_pid(Pid) ->
+ rh_print ! kill
+ end.
+
+%% Create the first target release, vsn P1G. This release is used for
+%% all test cases in {group,release}
+create_p1g(Conf,Sname) ->
+ do_create_p1g(Conf,filename:join(priv_dir(Conf),Sname)).
+
+do_create_p1g(Conf,TargetDir) ->
+ PrivDir = priv_dir(Conf),
+ DataDir = ?config(data_dir,Conf),
+ ErtsVsn = "4.4",
+ ErtsDir = "erts-"++ErtsVsn,
+
+ %% Create dirs
+ BinDir = filename:join(TargetDir,bin),
+ ReleasesDir = filename:join(TargetDir,releases),
+ LogDir = filename:join(TargetDir,log),
+ ok = filelib:ensure_dir(filename:join(BinDir,"*")),
+ ok = filelib:ensure_dir(filename:join(ReleasesDir,"*")),
+ ok = filelib:ensure_dir(filename:join(LogDir,"*")),
+
+ %% Copy stuff
+ ErtsLatest = latest_version(filename:join(code:root_dir(),"erts")),
+ ok = copy_tree(Conf, ErtsLatest, ErtsDir, TargetDir),
+ ErtsBinDir = filename:join([TargetDir,ErtsDir,bin]),
+ copy_file(filename:join([ErtsBinDir, "epmd"]), BinDir, [preserve]),
+ copy_file(filename:join([ErtsBinDir, "run_erl"]), BinDir, [preserve]),
+ copy_file(filename:join([ErtsBinDir, "to_erl"]), BinDir, [preserve]),
+
+ copy_file(filename:join(DataDir, "../installer.beam"),
+ filename:join([DataDir,lib,"installer-1.0",ebin])),
+
+ %% Create .rel, .script and .boot files
+ RelName = "rel0",
+ RelVsn = "P1G",
+ RelDir = filename:join(PrivDir,RelName),
+ RelFileName = filename:join(RelDir,RelName),
+ RelFile = RelFileName ++ ".rel",
+ ok = filelib:ensure_dir(RelFile),
+ LibPath = filename:join([DataDir,lib,"*",ebin]),
+
+ TarFile = create_basic_release(RelFile, RelVsn, {ErtsVsn,false},
+ LibPath, [], [], [], []),
+
+ %% Extract tar file in target directory (i.e. same directory as erts etc.)
+ ok = erl_tar:extract(TarFile, [{cwd, TargetDir}, compressed]),
+
+ %% Create start_erl.data
+ StartErlDataFile = filename:join([ReleasesDir, "start_erl.data"]),
+ StartErlData = io_lib:fwrite("~s ~s~n", [ErtsVsn, RelVsn]),
+ ok = file:write_file(StartErlDataFile, StartErlData),
+
+ %% Create RELEASES
+ ok = release_handler:create_RELEASES(TargetDir,ReleasesDir,RelFile,[]),
+
+ %% Create start_erl
+ ok = subst_file(filename:join([ErtsBinDir,"start_erl.src"]),
+ filename:join([BinDir,"start_erl"]),
+ [{"EMU","beam"}],
+ [{chmod,8#0755}]),
+
+ %% Create start script
+ %% Using a customized start script from DataDir where some options
+ %% (heart and nodename) are added compared to the start.src in the
+ %% erlang distribution.
+ ok = subst_file(filename:join(DataDir, "start"),
+ filename:join([BinDir, "start"]),
+ [{"ROOT",TargetDir}],
+ [preserve]),
+ ok.
+
+%% Create version P1H - which is P1G + a-1.0
+%% Must have run create_p1g first!!
+create_p1h(Conf) ->
+ create_upgrade_release(Conf,"rel1","P1H",{"4.4",false},[{a,"1.0"}],
+ [{a,[{key2,val2}]}],{"rel0",[new_appl]}).
+
+%% Create version P1I - which is P1H, but with application a upgraded to a-1.1
+%% Must have run create_p1h first!!
+create_p1i(Conf) ->
+ create_upgrade_release(Conf,"rel2","P1I",{"4.4",false},[{a,"1.1"}],
+ [{a,[{key2,newval2}]}],
+ {"rel1",[{extra,gott}]}).
+
+%% Create version P2A - which is P1I, but with erts-<latest>
+%% Must have run create_p1i first!!
+create_p2a(Conf) ->
+ ErtsVsn = erlang:system_info(version),
+ create_upgrade_release(Conf,"rel3","P2A",{ErtsVsn,code:root_dir()},
+ [{a,"1.1"}],[{a,[{key2,newval2}]}],
+ {"rel2",[new_emu]}).
+
+%% Create a release tar package which can be installed on top of P1G
+create_upgrade_release(Conf,RelName,RelVsn,Erts,Apps,Config,{UpFromName,Descr}) ->
+ PrivDir = priv_dir(Conf),
+ DataDir = ?config(data_dir,Conf),
+
+ RelDir = filename:join(PrivDir,RelName),
+ RelFileName = filename:join(RelDir,RelName),
+ RelFile = RelFileName ++ ".rel",
+ ok = filelib:ensure_dir(RelFile),
+ LibPath = filename:join([DataDir,lib,"*",ebin]),
+
+ UpFrom = [{filename:join([PrivDir,UpFromName,UpFromName]),Descr}],
+
+ create_basic_release(RelFile, RelVsn, Erts, LibPath,
+ Apps, Config, UpFrom, []),
+ ok.
+
+%% Create .rel, .script, .boot, sys.config and tar
+create_basic_release(RelFile,RelVsn,{ErtsVsn,ErtsDir},LibPath,ExtraApps,Config,UpFrom,DownTo) ->
+ RelDir = filename:dirname(RelFile),
+ RelFileName = filename:rootname(RelFile),
+
+ %% Create .rel file
+ create_installer_rel_file(RelFile,RelVsn,ErtsVsn,ExtraApps),
+
+ %% Generate .script and .boot
+ ok = systools:make_script(RelFileName,
+ [{path,[LibPath]},
+ {outdir,RelDir}]),
+
+ %% Generate relup
+ ok = systools:make_relup(RelFileName,UpFrom,DownTo,[{path,[LibPath]},
+ {outdir,RelDir}]),
+
+ %% Create sys.config
+ ok = write_term_file(filename:join(RelDir,"sys.config"),Config),
+
+
+ %% Create tar file (i.e. collect all lib/app-*/* and system files)
+ ok = systools:make_tar(RelFileName,
+ [{path,[LibPath]},
+ {outdir,RelDir} |
+ case ErtsDir of
+ false -> [];
+ _ -> [{erts,ErtsDir}]
+ end]),
+
+ RelFileName ++ ".tar.gz".
+
+%% Create a .rel file
+create_installer_rel_file(RelFile,RelVsn,ErtsVsn,ExtraApps) ->
+ create_rel_file(RelFile,"SASL-test",RelVsn,ErtsVsn,
+ [{installer,"1.0"}|ExtraApps]).
+
+create_rel_file(RelFile,RelName,RelVsn,ErtsVsn,ExtraApps) ->
+ {ok,KernelVsn} = application:get_key(kernel,vsn),
+ {ok,StdlibVsn} = application:get_key(stdlib,vsn),
+ {ok,SaslVsn} = application:get_key(sasl,vsn),
+ application:load(tools),
+ {ok,ToolsVsn} = application:get_key(tools,vsn),
+ application:load(runtime_tools),
+ {ok,RuntimeToolsVsn} = application:get_key(runtime_tools,vsn),
+
+ RelFileContent = {release,
+ {RelName, RelVsn},
+ {erts, ErtsVsn},
+ [{kernel, KernelVsn},
+ {stdlib, StdlibVsn},
+ {sasl, SaslVsn},
+ {runtime_tools, RuntimeToolsVsn},
+ {tools, ToolsVsn} |
+ ExtraApps]},
+ ok = write_term_file(RelFile,RelFileContent).
+
+%% Insert a term in a file, which can be read with file:consult/1.
+write_term_file(File,Term) ->
+ ok = file:write_file(File,io_lib:format("~p.~n",[Term])).
+
+
+%% Check that global group info is correct
+check_gg_info(Node,OtherAlive,OtherDead,Synced) ->
+ GGI = rpc:call(Node, global_group, info, []),
+ GI = rpc:call(Node, global, info,[]),
+ try do_check_gg_info(OtherAlive,OtherDead,Synced,GGI,GI)
+ catch _:E ->
+ ?t:format("~ncheck_gg_info failed for ~p: ~p~nwhen GGI was: ~p~n"
+ "and GI was: ~p~n",
+ [Node,E,GGI,GI]),
+ ?t:fail("check_gg_info failed")
+ end.
+
+do_check_gg_info(OtherAlive,OtherDead,Synced,GGI,GI) ->
+ {_,gg1} = lists:keyfind(own_group_name,1,GGI),
+ {_,synced} = lists:keyfind(state,1,GGI),
+ {_,AllNodes} = lists:keyfind(own_group_nodes,1,GGI),
+ true = lists:sort(AllNodes) =:= lists:sort(OtherAlive++OtherDead),
+ {_,[]} = lists:keyfind(sync_error,1,GGI),
+ {_,[{gg2,[_,_]}]} = lists:keyfind(other_groups,1,GGI),
+
+ %% There is a known bug in global_group (OTP-9177) which causes
+ %% the following to fail every now and then:
+ %% {_,SyncedNodes} = lists:keyfind(synced_nodes,1,GGI),
+ %% true = lists:sort(SyncedNodes) =:= lists:sort(Synced),
+ %% {_,NoContact} = lists:keyfind(no_contact,1,GGI),
+ %% true = lists:sort(NoContact) =:= lists:sort(OtherDead),
+
+ %% Therefore we use global:info instead for this part
+ {state,_,_,SyncedNodes,_,_,_,_,_,_,_} = GI,
+ true = lists:sort(SyncedNodes) =:= lists:sort(Synced),
+
+ %% .. and we only check that all OtherDead are listed as
+ %% no_contact (due to th bug there might be more nodes in this
+ %% list)
+ {_,NoContact} = lists:keyfind(no_contact,1,GGI),
+ true =
+ lists:sort(OtherDead) =:=
+ lists:sort([NC || NC <- NoContact,lists:member(NC,OtherDead)]),
+
+ ok.
+
+%% Return the configuration (to be inserted in sys.config) for global group tests
+gg_config(Snames) ->
+ Nodes = [node_name(Sname) || Sname <- Snames],
+ [{kernel, [{sync_nodes_optional, Nodes},
+ {sync_nodes_timeout, 10000},
+ {global_groups,
+ [{gg1, Nodes},
+ {gg2, [node_name(Sname) || Sname <- [ggq,ggw]]}]}]},
+ {a, [{key2, val2}]}].
+
+%% Start a node with short name SnameStr, and unpack P1H
+unpack_p1h(Conf,Sname) ->
+ PrivDir = priv_dir(Conf),
+ [Node] = start_nodes(Conf,[Sname],"create_p1h"),
+ ok = rpc_inst(Node, unpack_p1h, [PrivDir]),
+ Node.
+
+%% On the given node, install P1H and make it permanent
+%% This function is to be called after unpack_p1h/2, with the same node.
+permanent_p1h(Node) ->
+ ok = rpc_inst(Node, permanent_p1h, []).
+
+%% For each node in ToNodes, create a target installation which is
+%% indentical to the target installation for FromNode.
+copy_installed(Conf,FromNode,ToNodes) ->
+ PrivDir = priv_dir(Conf),
+ DataDir = ?config(data_dir,Conf),
+ lists:foreach(
+ fun(Node) ->
+ ok = copy_tree(Conf,filename:join(PrivDir,FromNode),Node,PrivDir),
+ NodeDir = filename:join(PrivDir,Node),
+ ok = subst_file(filename:join(DataDir, "start"),
+ filename:join([NodeDir, "bin", "start"]),
+ [{"ROOT",NodeDir}]),
+ LogDir = filename:join(NodeDir,log),
+ {ok,Logs} = file:list_dir(LogDir),
+ lists:foreach(fun(Log) ->
+ file:delete(filename:join(LogDir,Log))
+ end,
+ Logs)
+ end,
+ ToNodes).
+
+start_nodes(Conf,Snames,Tag) ->
+ PrivDir = priv_dir(Conf),
+ Nodes =
+ lists:map(
+ fun(Sname) ->
+ NodeDir = filename:join(PrivDir,Sname),
+ Node = node_name(Sname),
+
+ Script = filename:join([NodeDir,"bin","start"]),
+ Cmd = "env NODENAME="++atom_to_list(Sname) ++ " " ++ Script,
+ %% {ok,StartFile} = file:read_file(Cmd),
+ %% io:format("~s:\n~s~n~n",[Start,binary_to_list(StartFile)]),
+ Res = os:cmd(Cmd),
+ io:format("Start ~p: ~p~n=>\t~p~n", [Sname,Cmd,Res]),
+ Node
+ end,
+ Snames),
+ wait_nodes_up(Nodes,Tag),
+ Nodes.
+
+tc_sname(Config) ->
+ tc_sname(Config,"").
+tc_sname(Config,Fix) when is_atom(Fix) ->
+ tc_sname(Config,atom_to_list(Fix));
+tc_sname(Config,Fix) when is_list(Fix) ->
+ list_to_atom(
+ atom_to_list(?MODULE)
+ ++ "-" ++ atom_to_list(?config(sname_prefix, Config)) ++
+ case Fix of
+ "" -> "";
+ _ -> "-" ++ Fix
+ end).
+
+tc_full_node_name(Config) ->
+ tc_full_node_name(Config,"").
+tc_full_node_name(Config,Fix) ->
+ node_name(tc_sname(Config,Fix)).
+
+
+%% When installing a release for which the sys.config includes added
+%% or changed global group(s), this node (test_sever@host) will be
+%% disconnected from the test node (Node) by global_group.erl. This
+%% will cause the rpc:call to terminate with {badrpc,nodedown} even if
+%% the installation succeeds. This function installs the release,
+%% accepts the faulty return value and then checks if the release was
+%% successfully installed.
+install_release_changed_gg(Node,RelVsn) ->
+ stop_cover(Node),
+ {badrpc,nodedown} = rpc:call(Node,release_handler,install_release,[RelVsn]),
+ timer:sleep(100),
+ wait_installed(Node,RelVsn,4).
+
+wait_installed(Node,RelVsn,0) ->
+ ?t:fail("install_release_changed_gg failed for " ++ RelVsn ++
+ " on " ++ atom_to_list(Node));
+wait_installed(Node,RelVsn,N) ->
+ Rels = rpc:call(Node,release_handler,which_releases,[]),
+ case lists:keyfind(RelVsn, 2, Rels) of
+ {"SASL-test", RelVsn, _Libs, current} ->
+ start_cover(Node),
+ ok;
+ _ ->
+ timer:sleep(500),
+ wait_installed(Node,RelVsn,N-1)
+ end.
+
+%% Start/stop cover measurements on the given node
+start_cover(Node) ->
+ cover_fun(Node,start).
+stop_cover(Node) ->
+ cover_fun(Node,stop).
+
+cover_fun(Node,Func) ->
+ case ?t:is_cover() of
+ true ->
+ cover:Func(Node);
+ false ->
+ ok
+ end.
+
+%%%-----------------------------------------------------------------
+%%% Create a fake release ....
+
+%% This function will create and install a release build on the
+%% current running OTP release. It includes kernel, stdlib and sasl,
+%% and possibly other applications if they are listed in AppDirs =
+%% [{App,Vsn,LibDir}]
+create_and_install_fake_first_release(Dir,AppDirs) ->
+ %% Create the first release
+ {RelName,RelVsn} = init:script_id(),
+ {Rel,_} = create_fake_release(Dir,RelName,RelVsn,AppDirs),
+ ReleasesDir = filename:join(Dir, "releases"),
+ RelDir = filename:dirname(Rel),
+
+ %% And install it
+ RelVsnDir = filename:join(ReleasesDir, RelVsn),
+ ok = filelib:ensure_dir(filename:join(RelVsnDir,"*")),
+
+ ok = copy_file(Rel++".rel",RelVsnDir),
+ ok = copy_file(Rel++".boot",filename:join(RelVsnDir, "start.boot")),
+ ok = copy_file(filename:join(RelDir,"sys.config"),RelVsnDir),
+
+ ok = release_handler:create_RELEASES(code:root_dir(),
+ ReleasesDir,
+ Rel++".rel",
+ AppDirs),
+
+ Rel.
+
+%% This function create a new release, including a relup file. It can
+%% be upgraded to from the release created by
+%% create_and_install_fake_first_release/2. Unpack first by calls to
+%% release_handler:set_unpacked and release_handler:install_file.
+create_fake_upgrade_release(Dir,RelVsn,AppDirs,{UpFrom,DownTo,ExtraLibs}) ->
+ %% Create a new release
+ {RelName,_} = init:script_id(),
+ {Rel,Paths} = create_fake_release(Dir,RelName,RelVsn,AppDirs),
+ RelDir = filename:dirname(Rel),
+
+ %% And a relup file so it can be upgraded to
+ RelupPath = Paths ++ [filename:join([Lib,"*","ebin"]) || Lib <- ExtraLibs],
+ ok = systools:make_relup(Rel,[UpFrom],[DownTo],
+ [{path,RelupPath},
+ {outdir,RelDir}]),
+
+ Rel.
+
+
+create_fake_release(Dir,RelName,RelVsn,AppDirs) ->
+ %% Create .rel files
+ RelDir = filename:join(Dir,"rel_" ++ RelVsn),
+ Rel = filename:join([RelDir,"rel_" ++ RelVsn]),
+ ok = filelib:ensure_dir(Rel),
+ ErtsVsn = erlang:system_info(version),
+
+ {Apps,Paths} =
+ lists:foldl(fun({App,Vsn,Lib},{As,Ps}) ->
+ {[{App,Vsn}|As],
+ lists:umerge([filename:join([Lib,"*",ebin])],Ps)}
+ end,
+ {[],[]},
+ AppDirs),
+
+ create_rel_file(Rel++".rel",RelName,RelVsn,ErtsVsn,Apps),
+
+ %% Generate boot scripts
+ ok = systools:make_script(Rel,[local,
+ {path, Paths},
+ {outdir,RelDir}]),
+ ok = copy_file(Rel++".boot", filename:join(RelDir,"start.boot")),
+
+ %% Use an own 'releases' directory - we don't want to change the
+ %% contents of $OTP_ROOT/releases
+ %% Inform SASL about this via sys.config
+ ReleasesDir = filename:join(Dir, "releases"),
+ Config = [{sasl,[{releases_dir,ReleasesDir}]}],
+ ok = write_term_file(filename:join(RelDir,"sys.config"), Config),
+
+ {Rel,Paths}.
+
+
+rpc_inst(Node,Func,Args) ->
+ rpc:call(Node,installer,Func,[node()|Args]).
diff --git a/lib/sasl/test/release_handler_SUITE_data/Makefile.src b/lib/sasl/test/release_handler_SUITE_data/Makefile.src
new file mode 100644
index 0000000000..85e25fdc2f
--- /dev/null
+++ b/lib/sasl/test/release_handler_SUITE_data/Makefile.src
@@ -0,0 +1,108 @@
+EFLAGS=+debug_info
+
+P2B= \
+ P2B/a-2.0/ebin/a.beam \
+ P2B/a-2.0/ebin/a_sup.beam
+
+LIB= \
+ lib/a-1.1/ebin/a.beam \
+ lib/a-1.1/ebin/a_sup.beam \
+ lib/a-1.0/ebin/a.beam \
+ lib/a-1.0/ebin/a_sup.beam \
+
+APP= \
+ app1_app2/lib1/app1-1.0/ebin/app1_sup.@EMULATOR@ \
+ app1_app2/lib1/app1-1.0/ebin/app1_server.@EMULATOR@ \
+ app1_app2/lib1/app1-1.0/ebin/app1.@EMULATOR@ \
+ app1_app2/lib1/app2-1.0/ebin/app2_sup.@EMULATOR@ \
+ app1_app2/lib1/app2-1.0/ebin/app2_server.@EMULATOR@ \
+ app1_app2/lib1/app2-1.0/ebin/app2.@EMULATOR@ \
+ app1_app2/lib2/app1-2.0/ebin/app1_sup.@EMULATOR@ \
+ app1_app2/lib2/app1-2.0/ebin/app1_server.@EMULATOR@ \
+ app1_app2/lib2/app1-2.0/ebin/app1.@EMULATOR@ \
+ app1_app2/lib2/app2-1.0/ebin/app2_sup.@EMULATOR@ \
+ app1_app2/lib2/app2-1.0/ebin/app2_server.@EMULATOR@ \
+ app1_app2/lib2/app2-1.0/ebin/app2.@EMULATOR@
+
+OTP2740= \
+ otp_2740/vsn_atom.@EMULATOR@ \
+ otp_2740/vsn_list.@EMULATOR@ \
+ otp_2740/vsn_numeric.@EMULATOR@ \
+ otp_2740/vsn_tuple.@EMULATOR@ \
+ otp_2740/vsn_string.@EMULATOR@
+
+C= \
+ c/aa.@EMULATOR@ \
+ c/b.@EMULATOR@ \
+ c/c_sup.@EMULATOR@
+
+
+all: $(P2B) $(LIB) $(APP) $(OTP2740) $(C)
+
+P2B/a-2.0/ebin/a.@EMULATOR@: P2B/a-2.0/src/a.erl
+ erlc $(EFLAGS) -oP2B/a-2.0/ebin P2B/a-2.0/src/a.erl
+P2B/a-2.0/ebin/a_sup.@EMULATOR@: P2B/a-2.0/src/a_sup.erl
+ erlc $(EFLAGS) -oP2B/a-2.0/ebin P2B/a-2.0/src/a_sup.erl
+
+
+lib/a-1.0/ebin/a.@EMULATOR@: lib/a-1.0/src/a.erl
+ erlc $(EFLAGS) -olib/a-1.0/ebin lib/a-1.0/src/a.erl
+lib/a-1.0/ebin/a_sup.@EMULATOR@: lib/a-1.0/src/a_sup.erl
+ erlc $(EFLAGS) -olib/a-1.0/ebin lib/a-1.0/src/a_sup.erl
+
+
+lib/a-1.1/ebin/a.@EMULATOR@: lib/a-1.1/src/a.erl
+ erlc $(EFLAGS) -olib/a-1.1/ebin lib/a-1.1/src/a.erl
+lib/a-1.1/ebin/a_sup.@EMULATOR@: lib/a-1.1/src/a_sup.erl
+ erlc $(EFLAGS) -olib/a-1.1/ebin lib/a-1.1/src/a_sup.erl
+
+
+app1_app2/lib1/app1-1.0/ebin/app1_sup.@EMULATOR@: app1_app2/lib1/app1-1.0/src/app1_sup.erl
+ erlc $(EFLAGS) -oapp1_app2/lib1/app1-1.0/ebin app1_app2/lib1/app1-1.0/src/app1_sup.erl
+app1_app2/lib1/app1-1.0/ebin/app1_server.@EMULATOR@: app1_app2/lib1/app1-1.0/src/app1_server.erl
+ erlc $(EFLAGS) -oapp1_app2/lib1/app1-1.0/ebin app1_app2/lib1/app1-1.0/src/app1_server.erl
+app1_app2/lib1/app1-1.0/ebin/app1.@EMULATOR@: app1_app2/lib1/app1-1.0/src/app1.erl
+ erlc $(EFLAGS) -oapp1_app2/lib1/app1-1.0/ebin app1_app2/lib1/app1-1.0/src/app1.erl
+
+
+app1_app2/lib1/app2-1.0/ebin/app2_sup.@EMULATOR@: app1_app2/lib1/app2-1.0/src/app2_sup.erl
+ erlc $(EFLAGS) -oapp1_app2/lib1/app2-1.0/ebin app1_app2/lib1/app2-1.0/src/app2_sup.erl
+app1_app2/lib1/app2-1.0/ebin/app2_server.@EMULATOR@: app1_app2/lib1/app2-1.0/src/app2_server.erl
+ erlc $(EFLAGS) -oapp1_app2/lib1/app2-1.0/ebin app1_app2/lib1/app2-1.0/src/app2_server.erl
+app1_app2/lib1/app2-1.0/ebin/app2.@EMULATOR@: app1_app2/lib1/app2-1.0/src/app2.erl
+ erlc $(EFLAGS) -oapp1_app2/lib1/app2-1.0/ebin app1_app2/lib1/app2-1.0/src/app2.erl
+
+
+app1_app2/lib2/app1-2.0/ebin/app1_sup.@EMULATOR@: app1_app2/lib2/app1-2.0/src/app1_sup.erl
+ erlc $(EFLAGS) -oapp1_app2/lib2/app1-2.0/ebin app1_app2/lib2/app1-2.0/src/app1_sup.erl
+app1_app2/lib2/app1-2.0/ebin/app1_server.@EMULATOR@: app1_app2/lib2/app1-2.0/src/app1_server.erl
+ erlc $(EFLAGS) -oapp1_app2/lib2/app1-2.0/ebin app1_app2/lib2/app1-2.0/src/app1_server.erl
+app1_app2/lib2/app1-2.0/ebin/app1.@EMULATOR@: app1_app2/lib2/app1-2.0/src/app1.erl
+ erlc $(EFLAGS) -oapp1_app2/lib2/app1-2.0/ebin app1_app2/lib2/app1-2.0/src/app1.erl
+
+
+app1_app2/lib2/app2-1.0/ebin/app2_sup.@EMULATOR@: app1_app2/lib2/app2-1.0/src/app2_sup.erl
+ erlc $(EFLAGS) -oapp1_app2/lib2/app2-1.0/ebin app1_app2/lib2/app2-1.0/src/app2_sup.erl
+app1_app2/lib2/app2-1.0/ebin/app2_server.@EMULATOR@: app1_app2/lib2/app2-1.0/src/app2_server.erl
+ erlc $(EFLAGS) -oapp1_app2/lib2/app2-1.0/ebin app1_app2/lib2/app2-1.0/src/app2_server.erl
+app1_app2/lib2/app2-1.0/ebin/app2.@EMULATOR@: app1_app2/lib2/app2-1.0/src/app2.erl
+ erlc $(EFLAGS) -oapp1_app2/lib2/app2-1.0/ebin app1_app2/lib2/app2-1.0/src/app2.erl
+
+
+otp_2740/vsn_atom.@EMULATOR@: otp_2740/vsn_atom.erl
+ erlc $(EFLAGS) -ootp_2740 otp_2740/vsn_atom.erl
+otp_2740/vsn_list.@EMULATOR@: otp_2740/vsn_list.erl
+ erlc $(EFLAGS) -ootp_2740 otp_2740/vsn_list.erl
+otp_2740/vsn_numeric.@EMULATOR@: otp_2740/vsn_numeric.erl
+ erlc $(EFLAGS) -ootp_2740 otp_2740/vsn_numeric.erl
+otp_2740/vsn_tuple.@EMULATOR@: otp_2740/vsn_tuple.erl
+ erlc $(EFLAGS) -ootp_2740 otp_2740/vsn_tuple.erl
+otp_2740/vsn_string.@EMULATOR@: otp_2740/vsn_string.erl
+ erlc $(EFLAGS) -ootp_2740 otp_2740/vsn_string.erl
+
+c/aa.@EMULATOR@: c/aa.erl
+ erlc $(EFLAGS) -oc c/aa.erl
+c/b.@EMULATOR@: c/b.erl
+ erlc $(EFLAGS) -oc c/b.erl
+c/c_sup.@EMULATOR@: c/c_sup.erl
+ erlc $(EFLAGS) -oc c/c_sup.erl
diff --git a/lib/sasl/test/release_handler_SUITE_data/P2B/a-2.0/ebin/a.app b/lib/sasl/test/release_handler_SUITE_data/P2B/a-2.0/ebin/a.app
new file mode 100644
index 0000000000..200cfcfe47
--- /dev/null
+++ b/lib/sasl/test/release_handler_SUITE_data/P2B/a-2.0/ebin/a.app
@@ -0,0 +1,8 @@
+{application, a,
+ [{description, "A CXC 138 11"},
+ {vsn, "2.0"},
+ {modules, [{a, 1}, {a_sup,1}]},
+ {registered, [a_sup]},
+ {applications, [kernel, stdlib]},
+ {env, [{key1, val1}]},
+ {mod, {a_sup, []}}]}.
diff --git a/lib/sasl/test/release_handler_SUITE_data/P2B/a-2.0/src/a.erl b/lib/sasl/test/release_handler_SUITE_data/P2B/a-2.0/src/a.erl
new file mode 100644
index 0000000000..cfe38b55ce
--- /dev/null
+++ b/lib/sasl/test/release_handler_SUITE_data/P2B/a-2.0/src/a.erl
@@ -0,0 +1,47 @@
+%% ``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 via the world wide web 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.
+%%
+%% The Initial Developer of the Original Code is Ericsson Utvecklings AB.
+%% Portions created by Ericsson are Copyright 1999, Ericsson Utvecklings
+%% AB. All Rights Reserved.''
+%%
+%% $Id$
+%%
+-module(a).
+
+
+-behaviour(gen_server).
+
+%% External exports
+-export([start_link/0, a/0]).
+%% Internal exports
+-export([init/1, handle_call/3, handle_info/2, terminate/2]).
+
+start_link() -> gen_server:start_link({local, aa}, a, [], []).
+
+a() -> gen_server:call(aa, a).
+
+%%-----------------------------------------------------------------
+%% Callback functions from gen_server
+%%-----------------------------------------------------------------
+init([]) ->
+ process_flag(trap_exit, true),
+ {ok, state}.
+
+handle_call(a, _From, State) ->
+ X = application:get_all_env(a),
+ {reply, X, State}.
+
+handle_info(_, State) ->
+ {noreply, State}.
+
+terminate(_Reason, _State) ->
+ ok.
diff --git a/lib/sasl/test/release_handler_SUITE_data/P2B/a-2.0/src/a_sup.erl b/lib/sasl/test/release_handler_SUITE_data/P2B/a-2.0/src/a_sup.erl
new file mode 100644
index 0000000000..a141c1767b
--- /dev/null
+++ b/lib/sasl/test/release_handler_SUITE_data/P2B/a-2.0/src/a_sup.erl
@@ -0,0 +1,37 @@
+%% ``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 via the world wide web 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.
+%%
+%% The Initial Developer of the Original Code is Ericsson Utvecklings AB.
+%% Portions created by Ericsson are Copyright 1999, Ericsson Utvecklings
+%% AB. All Rights Reserved.''
+%%
+%% $Id$
+%%
+-module(a_sup).
+
+
+-behaviour(supervisor).
+
+%% External exports
+-export([start/2]).
+
+%% Internal exports
+-export([init/1]).
+
+start(_, _) ->
+ supervisor:start_link({local, a_sup}, a_sup, []).
+
+init([]) ->
+ SupFlags = {one_for_one, 4, 3600},
+ Config = {a,
+ {a, start_link, []},
+ permanent, 2000, worker, [a]},
+ {ok, {SupFlags, [Config]}}.
diff --git a/lib/sasl/test/release_handler_SUITE_data/app1_app2/lib1/app1-1.0/ebin/app1.app b/lib/sasl/test/release_handler_SUITE_data/app1_app2/lib1/app1-1.0/ebin/app1.app
new file mode 100644
index 0000000000..0489cb2595
--- /dev/null
+++ b/lib/sasl/test/release_handler_SUITE_data/app1_app2/lib1/app1-1.0/ebin/app1.app
@@ -0,0 +1,9 @@
+{application, app1,
+ [{description, "very simple example application"},
+ {id, "app1"},
+ {vsn, "1.0"},
+ {modules, [app1, app1_sup, app1_server]},
+ {registered, [harry]},
+ {applications, [kernel, stdlib, sasl]},
+ {env, [{var,val1}]},
+ {mod, {app1, []}}]}.
diff --git a/lib/sasl/test/release_handler_SUITE_data/app1_app2/lib1/app1-1.0/src/app1.erl b/lib/sasl/test/release_handler_SUITE_data/app1_app2/lib1/app1-1.0/src/app1.erl
new file mode 100644
index 0000000000..f123c8f470
--- /dev/null
+++ b/lib/sasl/test/release_handler_SUITE_data/app1_app2/lib1/app1-1.0/src/app1.erl
@@ -0,0 +1,22 @@
+-module(app1).
+
+-behaviour(application).
+
+%% Application callbacks
+-export([start/2, stop/1]).
+-export([config_change/3]).
+
+start(_Type, _StartArgs) ->
+ case app1_sup:start_link() of
+ {ok, Pid} ->
+ {ok, Pid};
+ Error ->
+ Error
+ end.
+
+stop(_State) ->
+ ok.
+
+config_change(Changed, _New, _Removed) ->
+ catch ets:insert(otp_6162, hd(Changed)),
+ ok.
diff --git a/lib/sasl/test/release_handler_SUITE_data/app1_app2/lib1/app1-1.0/src/app1_server.erl b/lib/sasl/test/release_handler_SUITE_data/app1_app2/lib1/app1-1.0/src/app1_server.erl
new file mode 100644
index 0000000000..9b49e772cc
--- /dev/null
+++ b/lib/sasl/test/release_handler_SUITE_data/app1_app2/lib1/app1-1.0/src/app1_server.erl
@@ -0,0 +1,32 @@
+-module(app1_server).
+
+-behaviour(gen_server).
+
+%% API
+-export([start_link/0]).
+
+%% gen_server callbacks
+-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
+ terminate/2, code_change/3]).
+
+start_link() ->
+ gen_server:start_link({local, harry}, ?MODULE, [], []).
+
+init([]) ->
+ {ok, []}.
+
+handle_call(_Request, _From, State) ->
+ Reply = ok,
+ {reply, Reply, State}.
+
+handle_cast(_Msg, State) ->
+ {noreply, State}.
+
+handle_info(_Info, State) ->
+ {noreply, State}.
+
+terminate(_Reason, _State) ->
+ ok.
+
+code_change(_OldVsn, State, _Extra) ->
+ {ok, State}.
diff --git a/lib/sasl/test/release_handler_SUITE_data/app1_app2/lib1/app1-1.0/src/app1_sup.erl b/lib/sasl/test/release_handler_SUITE_data/app1_app2/lib1/app1-1.0/src/app1_sup.erl
new file mode 100644
index 0000000000..e6ad9b6967
--- /dev/null
+++ b/lib/sasl/test/release_handler_SUITE_data/app1_app2/lib1/app1-1.0/src/app1_sup.erl
@@ -0,0 +1,17 @@
+-module(app1_sup).
+
+-behaviour(supervisor).
+
+%% API
+-export([start_link/0]).
+
+%% Supervisor callbacks
+-export([init/1]).
+
+start_link() ->
+ supervisor:start_link(?MODULE, []).
+
+init([]) ->
+ AChild = {harry,{app1_server,start_link,[]},
+ permanent,2000,worker,[app1_server]},
+ {ok,{{one_for_all,0,1}, [AChild]}}.
diff --git a/lib/sasl/test/release_handler_SUITE_data/app1_app2/lib1/app2-1.0/ebin/app2.app b/lib/sasl/test/release_handler_SUITE_data/app1_app2/lib1/app2-1.0/ebin/app2.app
new file mode 100644
index 0000000000..d48018cbda
--- /dev/null
+++ b/lib/sasl/test/release_handler_SUITE_data/app1_app2/lib1/app2-1.0/ebin/app2.app
@@ -0,0 +1,9 @@
+{application, app2,
+ [{description, "very simple example application"},
+ {id, "app2"},
+ {vsn, "1.0"},
+ {modules, [app2, app2_sup, app2_server]},
+ {registered, [ginny]},
+ {applications, [kernel, stdlib, sasl]},
+ {env, []},
+ {mod, {app2, []}}]}.
diff --git a/lib/sasl/test/release_handler_SUITE_data/app1_app2/lib1/app2-1.0/src/app2.erl b/lib/sasl/test/release_handler_SUITE_data/app1_app2/lib1/app2-1.0/src/app2.erl
new file mode 100644
index 0000000000..a41c39730c
--- /dev/null
+++ b/lib/sasl/test/release_handler_SUITE_data/app1_app2/lib1/app2-1.0/src/app2.erl
@@ -0,0 +1,17 @@
+-module(app2).
+
+-behaviour(application).
+
+%% Application callbacks
+-export([start/2, stop/1]).
+
+start(_Type, _StartArgs) ->
+ case app2_sup:start_link() of
+ {ok, Pid} ->
+ {ok, Pid};
+ Error ->
+ Error
+ end.
+
+stop(_State) ->
+ ok.
diff --git a/lib/sasl/test/release_handler_SUITE_data/app1_app2/lib1/app2-1.0/src/app2_server.erl b/lib/sasl/test/release_handler_SUITE_data/app1_app2/lib1/app2-1.0/src/app2_server.erl
new file mode 100644
index 0000000000..d8440230ff
--- /dev/null
+++ b/lib/sasl/test/release_handler_SUITE_data/app1_app2/lib1/app2-1.0/src/app2_server.erl
@@ -0,0 +1,32 @@
+-module(app2_server).
+
+-behaviour(gen_server).
+
+%% API
+-export([start_link/0]).
+
+%% gen_server callbacks
+-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
+ terminate/2, code_change/3]).
+
+start_link() ->
+ gen_server:start_link({local, ginny}, ?MODULE, [], []).
+
+init([]) ->
+ {ok, []}.
+
+handle_call(_Request, _From, State) ->
+ Reply = ok,
+ {reply, Reply, State}.
+
+handle_cast(_Msg, State) ->
+ {noreply, State}.
+
+handle_info(_Info, State) ->
+ {noreply, State}.
+
+terminate(_Reason, _State) ->
+ ok.
+
+code_change(_OldVsn, State, _Extra) ->
+ {ok, State}.
diff --git a/lib/sasl/test/release_handler_SUITE_data/app1_app2/lib1/app2-1.0/src/app2_sup.erl b/lib/sasl/test/release_handler_SUITE_data/app1_app2/lib1/app2-1.0/src/app2_sup.erl
new file mode 100644
index 0000000000..80b0952d4b
--- /dev/null
+++ b/lib/sasl/test/release_handler_SUITE_data/app1_app2/lib1/app2-1.0/src/app2_sup.erl
@@ -0,0 +1,17 @@
+-module(app2_sup).
+
+-behaviour(supervisor).
+
+%% API
+-export([start_link/0]).
+
+%% Supervisor callbacks
+-export([init/1]).
+
+start_link() ->
+ supervisor:start_link(?MODULE, []).
+
+init([]) ->
+ AChild = {ginny,{app2_server,start_link,[]},
+ permanent,2000,worker,[app2_server]},
+ {ok,{{one_for_all,0,1}, [AChild]}}.
diff --git a/lib/sasl/test/release_handler_SUITE_data/app1_app2/lib2/app1-2.0/ebin/app1.app b/lib/sasl/test/release_handler_SUITE_data/app1_app2/lib2/app1-2.0/ebin/app1.app
new file mode 100644
index 0000000000..3c65adfbb3
--- /dev/null
+++ b/lib/sasl/test/release_handler_SUITE_data/app1_app2/lib2/app1-2.0/ebin/app1.app
@@ -0,0 +1,9 @@
+{application, app1,
+ [{description, "very simple example application"},
+ {id, "app1"},
+ {vsn, "2.0"},
+ {modules, [app1, app1_sup, app1_server]},
+ {registered, [harry]},
+ {applications, [kernel, stdlib, sasl]},
+ {env, [{var,val2}]},
+ {mod, {app1, []}}]}.
diff --git a/lib/sasl/test/release_handler_SUITE_data/app1_app2/lib2/app1-2.0/ebin/app1.appup b/lib/sasl/test/release_handler_SUITE_data/app1_app2/lib2/app1-2.0/ebin/app1.appup
new file mode 100644
index 0000000000..e5e0cbda0e
--- /dev/null
+++ b/lib/sasl/test/release_handler_SUITE_data/app1_app2/lib2/app1-2.0/ebin/app1.appup
@@ -0,0 +1,4 @@
+{"2.0",
+ [{"1.0", [{load_module, app1_server}]}],
+ [{"1.0", [{load_module, app1_server}]}]
+}.
diff --git a/lib/sasl/test/release_handler_SUITE_data/app1_app2/lib2/app1-2.0/src/app1.erl b/lib/sasl/test/release_handler_SUITE_data/app1_app2/lib2/app1-2.0/src/app1.erl
new file mode 100644
index 0000000000..f123c8f470
--- /dev/null
+++ b/lib/sasl/test/release_handler_SUITE_data/app1_app2/lib2/app1-2.0/src/app1.erl
@@ -0,0 +1,22 @@
+-module(app1).
+
+-behaviour(application).
+
+%% Application callbacks
+-export([start/2, stop/1]).
+-export([config_change/3]).
+
+start(_Type, _StartArgs) ->
+ case app1_sup:start_link() of
+ {ok, Pid} ->
+ {ok, Pid};
+ Error ->
+ Error
+ end.
+
+stop(_State) ->
+ ok.
+
+config_change(Changed, _New, _Removed) ->
+ catch ets:insert(otp_6162, hd(Changed)),
+ ok.
diff --git a/lib/sasl/test/release_handler_SUITE_data/app1_app2/lib2/app1-2.0/src/app1_server.erl b/lib/sasl/test/release_handler_SUITE_data/app1_app2/lib2/app1-2.0/src/app1_server.erl
new file mode 100644
index 0000000000..660d095ebf
--- /dev/null
+++ b/lib/sasl/test/release_handler_SUITE_data/app1_app2/lib2/app1-2.0/src/app1_server.erl
@@ -0,0 +1,35 @@
+-module(app1_server).
+
+-behaviour(gen_server).
+
+%% API
+-export([start_link/0]).
+
+%% gen_server callbacks
+-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
+ terminate/2, code_change/3]).
+
+start_link() ->
+ gen_server:start_link({local, harry}, ?MODULE, [], []).
+
+init([]) ->
+ {ok, []}.
+
+handle_call(error, _From, State) ->
+ Reply = error,
+ {reply, Reply, State};
+handle_call(_Request, _From, State) ->
+ Reply = ok,
+ {reply, Reply, State}.
+
+handle_cast(_Msg, State) ->
+ {noreply, State}.
+
+handle_info(_Info, State) ->
+ {noreply, State}.
+
+terminate(_Reason, _State) ->
+ ok.
+
+code_change(_OldVsn, State, _Extra) ->
+ {ok, State}.
diff --git a/lib/sasl/test/release_handler_SUITE_data/app1_app2/lib2/app1-2.0/src/app1_sup.erl b/lib/sasl/test/release_handler_SUITE_data/app1_app2/lib2/app1-2.0/src/app1_sup.erl
new file mode 100644
index 0000000000..e6ad9b6967
--- /dev/null
+++ b/lib/sasl/test/release_handler_SUITE_data/app1_app2/lib2/app1-2.0/src/app1_sup.erl
@@ -0,0 +1,17 @@
+-module(app1_sup).
+
+-behaviour(supervisor).
+
+%% API
+-export([start_link/0]).
+
+%% Supervisor callbacks
+-export([init/1]).
+
+start_link() ->
+ supervisor:start_link(?MODULE, []).
+
+init([]) ->
+ AChild = {harry,{app1_server,start_link,[]},
+ permanent,2000,worker,[app1_server]},
+ {ok,{{one_for_all,0,1}, [AChild]}}.
diff --git a/lib/sasl/test/release_handler_SUITE_data/app1_app2/lib2/app2-1.0/ebin/app2.app b/lib/sasl/test/release_handler_SUITE_data/app1_app2/lib2/app2-1.0/ebin/app2.app
new file mode 100644
index 0000000000..d48018cbda
--- /dev/null
+++ b/lib/sasl/test/release_handler_SUITE_data/app1_app2/lib2/app2-1.0/ebin/app2.app
@@ -0,0 +1,9 @@
+{application, app2,
+ [{description, "very simple example application"},
+ {id, "app2"},
+ {vsn, "1.0"},
+ {modules, [app2, app2_sup, app2_server]},
+ {registered, [ginny]},
+ {applications, [kernel, stdlib, sasl]},
+ {env, []},
+ {mod, {app2, []}}]}.
diff --git a/lib/sasl/test/release_handler_SUITE_data/app1_app2/lib2/app2-1.0/src/app2.erl b/lib/sasl/test/release_handler_SUITE_data/app1_app2/lib2/app2-1.0/src/app2.erl
new file mode 100644
index 0000000000..a41c39730c
--- /dev/null
+++ b/lib/sasl/test/release_handler_SUITE_data/app1_app2/lib2/app2-1.0/src/app2.erl
@@ -0,0 +1,17 @@
+-module(app2).
+
+-behaviour(application).
+
+%% Application callbacks
+-export([start/2, stop/1]).
+
+start(_Type, _StartArgs) ->
+ case app2_sup:start_link() of
+ {ok, Pid} ->
+ {ok, Pid};
+ Error ->
+ Error
+ end.
+
+stop(_State) ->
+ ok.
diff --git a/lib/sasl/test/release_handler_SUITE_data/app1_app2/lib2/app2-1.0/src/app2_server.erl b/lib/sasl/test/release_handler_SUITE_data/app1_app2/lib2/app2-1.0/src/app2_server.erl
new file mode 100644
index 0000000000..d8440230ff
--- /dev/null
+++ b/lib/sasl/test/release_handler_SUITE_data/app1_app2/lib2/app2-1.0/src/app2_server.erl
@@ -0,0 +1,32 @@
+-module(app2_server).
+
+-behaviour(gen_server).
+
+%% API
+-export([start_link/0]).
+
+%% gen_server callbacks
+-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
+ terminate/2, code_change/3]).
+
+start_link() ->
+ gen_server:start_link({local, ginny}, ?MODULE, [], []).
+
+init([]) ->
+ {ok, []}.
+
+handle_call(_Request, _From, State) ->
+ Reply = ok,
+ {reply, Reply, State}.
+
+handle_cast(_Msg, State) ->
+ {noreply, State}.
+
+handle_info(_Info, State) ->
+ {noreply, State}.
+
+terminate(_Reason, _State) ->
+ ok.
+
+code_change(_OldVsn, State, _Extra) ->
+ {ok, State}.
diff --git a/lib/sasl/test/release_handler_SUITE_data/app1_app2/lib2/app2-1.0/src/app2_sup.erl b/lib/sasl/test/release_handler_SUITE_data/app1_app2/lib2/app2-1.0/src/app2_sup.erl
new file mode 100644
index 0000000000..80b0952d4b
--- /dev/null
+++ b/lib/sasl/test/release_handler_SUITE_data/app1_app2/lib2/app2-1.0/src/app2_sup.erl
@@ -0,0 +1,17 @@
+-module(app2_sup).
+
+-behaviour(supervisor).
+
+%% API
+-export([start_link/0]).
+
+%% Supervisor callbacks
+-export([init/1]).
+
+start_link() ->
+ supervisor:start_link(?MODULE, []).
+
+init([]) ->
+ AChild = {ginny,{app2_server,start_link,[]},
+ permanent,2000,worker,[app2_server]},
+ {ok,{{one_for_all,0,1}, [AChild]}}.
diff --git a/lib/sasl/test/release_handler_SUITE_data/c/aa.erl b/lib/sasl/test/release_handler_SUITE_data/c/aa.erl
new file mode 100644
index 0000000000..1c853c85b2
--- /dev/null
+++ b/lib/sasl/test/release_handler_SUITE_data/c/aa.erl
@@ -0,0 +1,41 @@
+%% ``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 via the world wide web 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.
+%%
+%% The Initial Developer of the Original Code is Ericsson Utvecklings AB.
+%% Portions created by Ericsson are Copyright 1999, Ericsson Utvecklings
+%% AB. All Rights Reserved.''
+%%
+%% $Id$
+%%
+-module(aa).
+
+
+-behaviour(gen_server).
+
+%% External exports
+-export([start_link/0]).
+%% Internal exports
+-export([init/1, handle_info/2, terminate/2]).
+
+start_link() -> gen_server:start_link({local, cc}, aa, [], []).
+
+%%-----------------------------------------------------------------
+%% Callback functions from gen_server
+%%-----------------------------------------------------------------
+init([]) ->
+ process_flag(trap_exit, true),
+ {ok, state}.
+
+handle_info(_, State) ->
+ {noreply, State}.
+
+terminate(_Reason, _State) ->
+ ok.
diff --git a/lib/sasl/test/release_handler_SUITE_data/c/b.erl b/lib/sasl/test/release_handler_SUITE_data/c/b.erl
new file mode 100644
index 0000000000..d8426a515e
--- /dev/null
+++ b/lib/sasl/test/release_handler_SUITE_data/c/b.erl
@@ -0,0 +1,38 @@
+%% ``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 via the world wide web 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.
+%%
+%% The Initial Developer of the Original Code is Ericsson Utvecklings AB.
+%% Portions created by Ericsson are Copyright 1999, Ericsson Utvecklings
+%% AB. All Rights Reserved.''
+%%
+%% $Id$
+%%
+-module(b).
+
+
+%% External exports
+-export([start_link/0]).
+%% Internal exports
+-export([init/0]).
+
+start_link() -> {ok, proc_lib:spawn_link(b, init, [])}.
+
+%%-----------------------------------------------------------------
+%% Callback functions from gen_server
+%%-----------------------------------------------------------------
+init() ->
+ register(bb, self()),
+ loop().
+
+loop() ->
+ receive
+ hej -> ok
+ end.
diff --git a/lib/sasl/test/release_handler_SUITE_data/c/c.app b/lib/sasl/test/release_handler_SUITE_data/c/c.app
new file mode 100644
index 0000000000..908a94cf2d
--- /dev/null
+++ b/lib/sasl/test/release_handler_SUITE_data/c/c.app
@@ -0,0 +1,8 @@
+{application, c,
+ [{description, "C CXC 138 11"},
+ {vsn, "1.0"},
+ {modules, [b, {aa, 1}, {c_sup,1}]},
+ {registered, [cc,bb,c_sup]},
+ {applications, [kernel, stdlib]},
+ {env, [{key1, val1}]},
+ {mod, {c_sup, []}}]}.
diff --git a/lib/sasl/test/release_handler_SUITE_data/c/c_sup.erl b/lib/sasl/test/release_handler_SUITE_data/c/c_sup.erl
new file mode 100644
index 0000000000..069eb3b99b
--- /dev/null
+++ b/lib/sasl/test/release_handler_SUITE_data/c/c_sup.erl
@@ -0,0 +1,40 @@
+%% ``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 via the world wide web 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.
+%%
+%% The Initial Developer of the Original Code is Ericsson Utvecklings AB.
+%% Portions created by Ericsson are Copyright 1999, Ericsson Utvecklings
+%% AB. All Rights Reserved.''
+%%
+%% $Id$
+%%
+-module(c_sup).
+
+
+-behaviour(supervisor).
+
+%% External exports
+-export([start/2]).
+
+%% Internal exports
+-export([init/1]).
+
+start(_, _) ->
+ supervisor:start_link({local, c_sup}, c_sup, []).
+
+init([]) ->
+ SupFlags = {one_for_one, 4, 3600},
+ Config1 = {c,
+ {aa, start_link, []},
+ permanent, 2000, worker, [aa]},
+ Config2 = {b,
+ {b, start_link, []},
+ permanent, 2000, worker, [b]},
+ {ok, {SupFlags, [Config1, Config2]}}.
diff --git a/lib/sasl/test/release_handler_SUITE_data/clients/start_cli1 b/lib/sasl/test/release_handler_SUITE_data/clients/start_cli1
new file mode 100755
index 0000000000..ee3d8c97cf
--- /dev/null
+++ b/lib/sasl/test/release_handler_SUITE_data/clients/start_cli1
@@ -0,0 +1,38 @@
+#!/bin/sh
+#
+# This program invokes the erlang emulator by calling run_erl.
+# It should only be used at an embedded target system.
+# It should be modified to give the correct flags to erl (via start_erl),
+# e.g -mode embedded -sname XXX
+#
+# Usage: start [Data]
+#
+
+if [ "x${NODENAME}" = "x" ]
+then
+ echo "ERROR: Variable \$NODENAME is not set!!"
+ exit 1
+fi
+
+TESTHOST=`hostname | sed 's/[.].*//'`
+IPADDR=%IPADDR%
+
+ROOTDIR=%ROOT%
+CLIENTDIR=$ROOTDIR/clients/type1/$NODENAME@$TESTHOST
+
+RELDIR=$CLIENTDIR/releases
+
+# Note that this scripts is modified an copied to $CLIENTDIR/bin/start
+# in release_handler_SUITE:copy_client - therefore HEART_COMMAND is as follows:
+HEART_COMMAND=$CLIENTDIR/bin/start
+HW_WD_DISABLE=true
+export HW_WD_DISABLE HEART_COMMAND
+
+START_ERL_DATA=${1:-$RELDIR/start_erl.data}
+
+if [ ! -d /tmp/$NODENAME@$TESTHOST ]
+then
+ mkdir /tmp/$NODENAME@$TESTHOST
+fi
+
+$ROOTDIR/bin/run_erl /tmp/$NODENAME@$TESTHOST/ $CLIENTDIR/log "exec $ROOTDIR/bin/start_erl $ROOTDIR $RELDIR $START_ERL_DATA -heart -sname $NODENAME -sasl start_prg \\\"$CLIENTDIR/bin/start\\\" masters \[\\'%MASTER%@$TESTHOST\\'\] client_directory \\\"$CLIENTDIR\\\" -loader inet -id $NODENAME -hosts $IPADDR" > $CLIENTDIR/log/run_erl.out 2>&1 &
diff --git a/lib/sasl/test/release_handler_SUITE_data/clients/start_cli2 b/lib/sasl/test/release_handler_SUITE_data/clients/start_cli2
new file mode 100755
index 0000000000..88912cf884
--- /dev/null
+++ b/lib/sasl/test/release_handler_SUITE_data/clients/start_cli2
@@ -0,0 +1,37 @@
+#!/bin/sh
+#
+# This program invokes the erlang emulator by calling run_erl.
+# It should only be used at an embedded target system.
+# It should be modified to give the correct flags to erl (via start_erl),
+# e.g -mode embedded -sname XXX
+#
+# Usage: start [Data]
+#
+
+if [ "x${NODENAME}" = "x" ]
+then
+ echo "ERROR: Variable \$NODENAME is not set!!"
+ exit 1
+fi
+
+TESTHOST=`hostname | sed 's/[.].*//'`
+
+ROOTDIR=%ROOT%
+CLIENTDIR=$ROOTDIR/clients/type1/$NODENAME@$TESTHOST
+
+RELDIR=$CLIENTDIR/releases
+
+# Note that this scripts is modified an copied to $CLIENTDIR/bin/start
+# in release_handler_SUITE:copy_client - therefore HEART_COMMAND is as follows:
+HEART_COMMAND=$CLIENTDIR/bin/start
+HW_WD_DISABLE=true
+export HW_WD_DISABLE HEART_COMMAND
+
+START_ERL_DATA=${1:-$RELDIR/start_erl.data}
+
+if [ ! -d /tmp/$NODENAME@$TESTHOST ]
+then
+ mkdir /tmp/$NODENAME@$TESTHOST
+fi
+
+$ROOTDIR/bin/run_erl /tmp/$NODENAME@$TESTHOST/ $CLIENTDIR/log "exec $ROOTDIR/bin/start_erl $ROOTDIR $RELDIR $START_ERL_DATA -heart -sname $NODENAME -sasl start_prg \\\"$CLIENTDIR/bin/start\\\" masters \[\\'%MASTER%@$TESTHOST\\',\\'master2@$TESTHOST\\'\] client_directory \\\"$CLIENTDIR\\\"" > /dev/null 2>&1 &
diff --git a/lib/sasl/test/release_handler_SUITE_data/lib/a-1.0/ebin/a.app b/lib/sasl/test/release_handler_SUITE_data/lib/a-1.0/ebin/a.app
new file mode 100644
index 0000000000..e938137f67
--- /dev/null
+++ b/lib/sasl/test/release_handler_SUITE_data/lib/a-1.0/ebin/a.app
@@ -0,0 +1,8 @@
+{application, a,
+ [{description, "A CXC 138 11"},
+ {vsn, "1.0"},
+ {modules, [{a, 1}, {a_sup,1}]},
+ {registered, [a_sup]},
+ {applications, [kernel, stdlib]},
+ {env, [{key1, val1}]},
+ {mod, {a_sup, []}}]}.
diff --git a/lib/sasl/test/release_handler_SUITE_data/lib/a-1.0/src/a.app b/lib/sasl/test/release_handler_SUITE_data/lib/a-1.0/src/a.app
new file mode 100644
index 0000000000..e938137f67
--- /dev/null
+++ b/lib/sasl/test/release_handler_SUITE_data/lib/a-1.0/src/a.app
@@ -0,0 +1,8 @@
+{application, a,
+ [{description, "A CXC 138 11"},
+ {vsn, "1.0"},
+ {modules, [{a, 1}, {a_sup,1}]},
+ {registered, [a_sup]},
+ {applications, [kernel, stdlib]},
+ {env, [{key1, val1}]},
+ {mod, {a_sup, []}}]}.
diff --git a/lib/sasl/test/release_handler_SUITE_data/lib/a-1.0/src/a.erl b/lib/sasl/test/release_handler_SUITE_data/lib/a-1.0/src/a.erl
new file mode 100644
index 0000000000..bb500bed69
--- /dev/null
+++ b/lib/sasl/test/release_handler_SUITE_data/lib/a-1.0/src/a.erl
@@ -0,0 +1,49 @@
+%% ``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 via the world wide web 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.
+%%
+%% The Initial Developer of the Original Code is Ericsson Utvecklings AB.
+%% Portions created by Ericsson are Copyright 1999, Ericsson Utvecklings
+%% AB. All Rights Reserved.''
+%%
+%% $Id$
+%%
+-module(a).
+
+
+-behaviour(gen_server).
+
+-vsn(1).
+
+%% External exports
+-export([start_link/0, a/0]).
+%% Internal exports
+-export([init/1, handle_call/3, handle_info/2, terminate/2]).
+
+start_link() -> gen_server:start_link({local, aa}, a, [], []).
+
+a() -> gen_server:call(aa, a).
+
+%%-----------------------------------------------------------------
+%% Callback functions from gen_server
+%%-----------------------------------------------------------------
+init([]) ->
+ process_flag(trap_exit, true),
+ {ok, state}.
+
+handle_call(a, _From, State) ->
+ X = application:get_all_env(a),
+ {reply, X, State}.
+
+handle_info(_, State) ->
+ {noreply, State}.
+
+terminate(_Reason, _State) ->
+ ok.
diff --git a/lib/sasl/test/release_handler_SUITE_data/lib/a-1.0/src/a_sup.erl b/lib/sasl/test/release_handler_SUITE_data/lib/a-1.0/src/a_sup.erl
new file mode 100644
index 0000000000..a141c1767b
--- /dev/null
+++ b/lib/sasl/test/release_handler_SUITE_data/lib/a-1.0/src/a_sup.erl
@@ -0,0 +1,37 @@
+%% ``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 via the world wide web 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.
+%%
+%% The Initial Developer of the Original Code is Ericsson Utvecklings AB.
+%% Portions created by Ericsson are Copyright 1999, Ericsson Utvecklings
+%% AB. All Rights Reserved.''
+%%
+%% $Id$
+%%
+-module(a_sup).
+
+
+-behaviour(supervisor).
+
+%% External exports
+-export([start/2]).
+
+%% Internal exports
+-export([init/1]).
+
+start(_, _) ->
+ supervisor:start_link({local, a_sup}, a_sup, []).
+
+init([]) ->
+ SupFlags = {one_for_one, 4, 3600},
+ Config = {a,
+ {a, start_link, []},
+ permanent, 2000, worker, [a]},
+ {ok, {SupFlags, [Config]}}.
diff --git a/lib/sasl/test/release_handler_SUITE_data/lib/a-1.1/ebin/a.app b/lib/sasl/test/release_handler_SUITE_data/lib/a-1.1/ebin/a.app
new file mode 100644
index 0000000000..1c3053b2fa
--- /dev/null
+++ b/lib/sasl/test/release_handler_SUITE_data/lib/a-1.1/ebin/a.app
@@ -0,0 +1,8 @@
+{application, a,
+ [{description, "A CXC 138 11"},
+ {vsn, "1.1"},
+ {modules, [{a, 2}, {a_sup,1}]},
+ {registered, [a_sup]},
+ {applications, [kernel, stdlib]},
+ {env, [{key1, val1}]},
+ {mod, {a_sup, []}}]}.
diff --git a/lib/sasl/test/release_handler_SUITE_data/lib/a-1.1/ebin/a.appup b/lib/sasl/test/release_handler_SUITE_data/lib/a-1.1/ebin/a.appup
new file mode 100644
index 0000000000..05db4cb541
--- /dev/null
+++ b/lib/sasl/test/release_handler_SUITE_data/lib/a-1.1/ebin/a.appup
@@ -0,0 +1,3 @@
+{"1.1",
+ [{"1.0",[{update,a,{advanced,extra_par}}]}],
+ []}.
diff --git a/lib/sasl/test/release_handler_SUITE_data/lib/a-1.1/src/a.erl b/lib/sasl/test/release_handler_SUITE_data/lib/a-1.1/src/a.erl
new file mode 100644
index 0000000000..c082ad5339
--- /dev/null
+++ b/lib/sasl/test/release_handler_SUITE_data/lib/a-1.1/src/a.erl
@@ -0,0 +1,54 @@
+%% ``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 via the world wide web 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.
+%%
+%% The Initial Developer of the Original Code is Ericsson Utvecklings AB.
+%% Portions created by Ericsson are Copyright 1999, Ericsson Utvecklings
+%% AB. All Rights Reserved.''
+%%
+%% $Id$
+%%
+-module(a).
+
+
+-behaviour(gen_server).
+
+%% External exports
+-export([start_link/0, a/0, b/0]).
+%% Internal exports
+-export([init/1, handle_call/3, handle_info/2, terminate/2, code_change/3]).
+
+start_link() -> gen_server:start_link({local, aa}, a, [], []).
+
+a() -> gen_server:call(aa, a).
+b() -> gen_server:call(aa, b).
+
+%%-----------------------------------------------------------------
+%% Callback functions from gen_server
+%%-----------------------------------------------------------------
+init([]) ->
+ process_flag(trap_exit, true),
+ {ok, {state, bval}}.
+
+handle_call(a, _From, State) ->
+ X = application:get_all_env(a),
+ {reply, X, State};
+
+handle_call(b, _From, State) ->
+ {reply, {ok, element(2, State)}, State}.
+
+handle_info(_, State) ->
+ {noreply, State}.
+
+terminate(_Reason, _State) ->
+ ok.
+
+code_change(1, Extra, State) ->
+ {ok, {state, bval}}.
diff --git a/lib/sasl/test/release_handler_SUITE_data/lib/a-1.1/src/a_sup.erl b/lib/sasl/test/release_handler_SUITE_data/lib/a-1.1/src/a_sup.erl
new file mode 100644
index 0000000000..a141c1767b
--- /dev/null
+++ b/lib/sasl/test/release_handler_SUITE_data/lib/a-1.1/src/a_sup.erl
@@ -0,0 +1,37 @@
+%% ``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 via the world wide web 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.
+%%
+%% The Initial Developer of the Original Code is Ericsson Utvecklings AB.
+%% Portions created by Ericsson are Copyright 1999, Ericsson Utvecklings
+%% AB. All Rights Reserved.''
+%%
+%% $Id$
+%%
+-module(a_sup).
+
+
+-behaviour(supervisor).
+
+%% External exports
+-export([start/2]).
+
+%% Internal exports
+-export([init/1]).
+
+start(_, _) ->
+ supervisor:start_link({local, a_sup}, a_sup, []).
+
+init([]) ->
+ SupFlags = {one_for_one, 4, 3600},
+ Config = {a,
+ {a, start_link, []},
+ permanent, 2000, worker, [a]},
+ {ok, {SupFlags, [Config]}}.
diff --git a/lib/sasl/test/release_handler_SUITE_data/lib/installer-1.0/ebin/installer.app b/lib/sasl/test/release_handler_SUITE_data/lib/installer-1.0/ebin/installer.app
new file mode 100644
index 0000000000..6f77317f6a
--- /dev/null
+++ b/lib/sasl/test/release_handler_SUITE_data/lib/installer-1.0/ebin/installer.app
@@ -0,0 +1,6 @@
+{application, installer,
+ [{description, "Installer application"},
+ {vsn, "1.0"},
+ {modules, [{installer, 1}]},
+ {registered, []},
+ {applications, [kernel, stdlib, sasl]}]}.
diff --git a/lib/sasl/test/release_handler_SUITE_data/lib/installer-1.0/src/installer.erl b/lib/sasl/test/release_handler_SUITE_data/lib/installer-1.0/src/installer.erl
new file mode 120000
index 0000000000..c2f93b822d
--- /dev/null
+++ b/lib/sasl/test/release_handler_SUITE_data/lib/installer-1.0/src/installer.erl
@@ -0,0 +1 @@
+../../../../installer.erl \ No newline at end of file
diff --git a/lib/sasl/test/release_handler_SUITE_data/otp_2740/vsn_atom.erl b/lib/sasl/test/release_handler_SUITE_data/otp_2740/vsn_atom.erl
new file mode 100644
index 0000000000..883688c231
--- /dev/null
+++ b/lib/sasl/test/release_handler_SUITE_data/otp_2740/vsn_atom.erl
@@ -0,0 +1,26 @@
+%% ``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 via the world wide web 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.
+%%
+%% The Initial Developer of the Original Code is Ericsson Utvecklings AB.
+%% Portions created by Ericsson are Copyright 1999, Ericsson Utvecklings
+%% AB. All Rights Reserved.''
+%%
+%% $Id$
+%%
+
+-module(vsn_atom).
+
+-vsn(atom).
+
+-export([ok/0]).
+
+ok() ->
+ ok.
diff --git a/lib/sasl/test/release_handler_SUITE_data/otp_2740/vsn_list.erl b/lib/sasl/test/release_handler_SUITE_data/otp_2740/vsn_list.erl
new file mode 100644
index 0000000000..34c38307ba
--- /dev/null
+++ b/lib/sasl/test/release_handler_SUITE_data/otp_2740/vsn_list.erl
@@ -0,0 +1,26 @@
+%% ``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 via the world wide web 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.
+%%
+%% The Initial Developer of the Original Code is Ericsson Utvecklings AB.
+%% Portions created by Ericsson are Copyright 1999, Ericsson Utvecklings
+%% AB. All Rights Reserved.''
+%%
+%% $Id$
+%%
+
+-module(vsn_list).
+
+-vsn([list, "of", {some, terms}]).
+
+-export([ok/0]).
+
+ok() ->
+ ok.
diff --git a/lib/sasl/test/release_handler_SUITE_data/otp_2740/vsn_numeric.erl b/lib/sasl/test/release_handler_SUITE_data/otp_2740/vsn_numeric.erl
new file mode 100644
index 0000000000..6bf52753fd
--- /dev/null
+++ b/lib/sasl/test/release_handler_SUITE_data/otp_2740/vsn_numeric.erl
@@ -0,0 +1,26 @@
+%% ``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 via the world wide web 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.
+%%
+%% The Initial Developer of the Original Code is Ericsson Utvecklings AB.
+%% Portions created by Ericsson are Copyright 1999, Ericsson Utvecklings
+%% AB. All Rights Reserved.''
+%%
+%% $Id$
+%%
+
+-module(vsn_numeric).
+
+-vsn(231894).
+
+-export([ok/0]).
+
+ok() ->
+ ok.
diff --git a/lib/sasl/test/release_handler_SUITE_data/otp_2740/vsn_string.erl b/lib/sasl/test/release_handler_SUITE_data/otp_2740/vsn_string.erl
new file mode 100644
index 0000000000..aa430a0bb3
--- /dev/null
+++ b/lib/sasl/test/release_handler_SUITE_data/otp_2740/vsn_string.erl
@@ -0,0 +1,26 @@
+%% ``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 via the world wide web 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.
+%%
+%% The Initial Developer of the Original Code is Ericsson Utvecklings AB.
+%% Portions created by Ericsson are Copyright 1999, Ericsson Utvecklings
+%% AB. All Rights Reserved.''
+%%
+%% $Id$
+%%
+
+-module(vsn_string).
+
+-vsn("a string").
+
+-export([ok/0]).
+
+ok() ->
+ ok.
diff --git a/lib/sasl/test/release_handler_SUITE_data/otp_2740/vsn_tuple.erl b/lib/sasl/test/release_handler_SUITE_data/otp_2740/vsn_tuple.erl
new file mode 100644
index 0000000000..3ff1018994
--- /dev/null
+++ b/lib/sasl/test/release_handler_SUITE_data/otp_2740/vsn_tuple.erl
@@ -0,0 +1,26 @@
+%% ``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 via the world wide web 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.
+%%
+%% The Initial Developer of the Original Code is Ericsson Utvecklings AB.
+%% Portions created by Ericsson are Copyright 1999, Ericsson Utvecklings
+%% AB. All Rights Reserved.''
+%%
+%% $Id$
+%%
+
+-module(vsn_tuple).
+
+-vsn({tuple, ["of", terms]}).
+
+-export([ok/0]).
+
+ok() ->
+ ok.
diff --git a/lib/sasl/test/release_handler_SUITE_data/start b/lib/sasl/test/release_handler_SUITE_data/start
new file mode 100755
index 0000000000..45e526c15f
--- /dev/null
+++ b/lib/sasl/test/release_handler_SUITE_data/start
@@ -0,0 +1,29 @@
+#!/bin/sh
+#
+# This program invokes the erlang emulator by calling run_erl.
+# It should only be used at an embedded target system.
+# It should be modified to give the correct flags to erl (via start_erl),
+# e.g -mode embedded -sname XXX
+#
+# Usage: start [Data]
+#
+ROOTDIR=%ROOT%
+
+if [ "x${NODENAME}" = "x" ]
+then
+ echo "ERROR: Variable \$NODENAME is not set!!"
+ exit 1
+fi
+
+if [ -z "$RELDIR" ]
+then
+ RELDIR=$ROOTDIR/releases
+fi
+
+HEART_COMMAND=$ROOTDIR/bin/start
+HW_WD_DISABLE=true
+export HW_WD_DISABLE HEART_COMMAND
+
+START_ERL_DATA=${1:-$RELDIR/start_erl.data}
+
+$ROOTDIR/bin/run_erl /tmp/ $ROOTDIR/log "exec $ROOTDIR/bin/start_erl $ROOTDIR $RELDIR $START_ERL_DATA -heart -sname $NODENAME" > $ROOTDIR/log/run_erl.out 2>&1 &
diff --git a/lib/sasl/test/release_handler_SUITE_data/target_system.erl b/lib/sasl/test/release_handler_SUITE_data/target_system.erl
new file mode 120000
index 0000000000..4d36c59632
--- /dev/null
+++ b/lib/sasl/test/release_handler_SUITE_data/target_system.erl
@@ -0,0 +1 @@
+../../examples/src/target_system.erl \ No newline at end of file
diff --git a/lib/sasl/test/sasl.cover b/lib/sasl/test/sasl.cover
new file mode 100644
index 0000000000..d19d3d0180
--- /dev/null
+++ b/lib/sasl/test/sasl.cover
@@ -0,0 +1,2 @@
+{incl_app,sasl,details}.
+
diff --git a/lib/sasl/test/sasl.spec b/lib/sasl/test/sasl.spec
new file mode 100644
index 0000000000..f3de90c9aa
--- /dev/null
+++ b/lib/sasl/test/sasl.spec
@@ -0,0 +1 @@
+{suites,"../sasl_test",all}.
diff --git a/lib/sasl/test/sasl_SUITE.erl b/lib/sasl/test/sasl_SUITE.erl
new file mode 100644
index 0000000000..454095db6a
--- /dev/null
+++ b/lib/sasl/test/sasl_SUITE.erl
@@ -0,0 +1,98 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2011. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(sasl_SUITE).
+-include_lib("common_test/include/ct.hrl").
+
+
+% Default timetrap timeout (set in init_per_testcase).
+-define(default_timeout, ?t:minutes(1)).
+-define(application, sasl).
+
+% Test server specific exports
+-export([all/0,groups/0,init_per_group/2,end_per_group/2]).
+-export([init_per_testcase/2, end_per_testcase/2]).
+
+% Test cases must be exported.
+-export([app_test/1,
+ log_mf_h_env/1]).
+
+all() ->
+ [app_test, log_mf_h_env].
+
+groups() ->
+ [].
+
+init_per_group(_GroupName, Config) ->
+ Config.
+
+end_per_group(_GroupName, Config) ->
+ Config.
+
+
+init_per_testcase(_Case, Config) ->
+ ?line Dog=test_server:timetrap(?default_timeout),
+ [{watchdog, Dog}|Config].
+end_per_testcase(_Case, Config) ->
+ Dog=?config(watchdog, Config),
+ test_server:timetrap_cancel(Dog),
+ ok.
+
+app_test(Config) when is_list(Config) ->
+ ?line ?t:app_test(sasl, allow),
+ ok.
+
+%% OTP-9185 - fail sasl start if some but not all log_mf_h env vars
+%% are given.
+log_mf_h_env(Config) ->
+ PrivDir = ?config(priv_dir,Config),
+ LogDir = filename:join(PrivDir,sasl_SUITE_log_dir),
+ ok = file:make_dir(LogDir),
+ application:stop(sasl),
+ SaslEnv = application:get_all_env(sasl),
+ lists:foreach(fun({E,_V}) -> application:unset_env(sasl,E) end, SaslEnv),
+
+ ok = application:set_env(sasl,error_logger_mf_dir,LogDir),
+ match_error(missing_config,application:start(sasl)),
+
+ ok = application:set_env(sasl,error_logger_mf_maxbytes,"xx"),
+ match_error(bad_config,application:start(sasl)),
+
+ ok = application:set_env(sasl,error_logger_mf_maxbytes,50000),
+ match_error(missing_config,application:start(sasl)),
+
+ ok = application:set_env(sasl,error_logger_mf_maxfiles,"xx"),
+ match_error(bad_config,application:start(sasl)),
+
+ ok = application:set_env(sasl,error_logger_mf_maxfiles,2),
+ ok = application:unset_env(sasl,error_logger_mf_dir),
+ match_error(missing_config,application:start(sasl)),
+
+ ok = application:set_env(sasl,error_logger_mf_dir,xx),
+ match_error(bad_config,application:start(sasl)),
+
+ ok = application:set_env(sasl,error_logger_mf_dir,LogDir),
+ ok = application:start(sasl).
+
+
+%%-----------------------------------------------------------------
+%% Internal
+match_error(Expected,{error,{bad_return,{_,{'EXIT',{Expected,{sasl,_}}}}}}) ->
+ ok;
+match_error(Expected,Actual) ->
+ ?t:fail({unexpected_return,Expected,Actual}).
diff --git a/lib/sasl/test/systools_SUITE.erl b/lib/sasl/test/systools_SUITE.erl
new file mode 100644
index 0000000000..9190b111ef
--- /dev/null
+++ b/lib/sasl/test/systools_SUITE.erl
@@ -0,0 +1,2112 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 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%
+%%
+%%
+%% Test suite for the systools module.
+%%
+%% The systools module is a wrapper for a number of modules that
+%% handle large parts of the release building functionality
+%% (e.g. checking app files, building a tar file, building
+%% release upgrad scripts.
+%%
+
+
+-module(systools_SUITE).
+
+%-define(debug, true).
+
+-include_lib("test_server/include/test_server.hrl").
+-define(format(S, A), ok).
+-define(datadir, ?config(data_dir, Config)).
+-define(privdir, ?config(priv_dir, Config)).
+-define(copydir, ?config(copy_dir, Config)).
+
+-include_lib("kernel/include/file.hrl").
+
+-export([all/0,suite/0,groups/0,init_per_group/2,end_per_group/2]).
+
+-export([ script_options/1, normal_script/1, no_mod_vsn_script/1,
+ wildcard_script/1, variable_script/1,
+ abnormal_script/1, src_tests_script/1, crazy_script/1,
+ warn_shadow_script/1,
+ included_script/1, included_override_script/1,
+ included_fail_script/1, included_bug_script/1, exref_script/1]).
+-export([ tar_options/1, normal_tar/1, no_mod_vsn_tar/1, variable_tar/1,
+ src_tests_tar/1, shadow_tar/1, var_tar/1,
+ exref_tar/1, link_tar/1]).
+-export([ normal_relup/1, abnormal_relup/1, no_appup_relup/1,
+ bad_appup_relup/1, app_start_type_relup/1, otp_3065/1]).
+-export([
+ otp_6226/1]).
+-export([init_per_suite/1, end_per_suite/1,
+ init_per_testcase/2, end_per_testcase/2]).
+
+-import(lists, [foldl/3]).
+
+-define(default_timeout, ?t:minutes(20)).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+suite() ->
+ [{ct_hooks, [ts_install_cth]}].
+
+all() ->
+ [{group, script}, {group, tar}, {group, relup},
+ {group, tickets}].
+
+groups() ->
+ [{script, [],
+ [script_options, normal_script, no_mod_vsn_script,
+ wildcard_script, variable_script, abnormal_script,
+ src_tests_script, crazy_script, warn_shadow_script,
+ included_script, included_override_script,
+ included_fail_script, included_bug_script, exref_script,
+ otp_3065]},
+ {tar, [],
+ [tar_options, normal_tar, no_mod_vsn_tar, variable_tar,
+ src_tests_tar, shadow_tar, var_tar,
+ exref_tar, link_tar]},
+ {relup, [],
+ [normal_relup, abnormal_relup, no_appup_relup,
+ bad_appup_relup, app_start_type_relup]},
+ {tickets, [], [otp_6226]}].
+
+init_per_group(_GroupName, Config) ->
+ Config.
+
+end_per_group(_GroupName, Config) ->
+ Config.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+
+init_per_suite(Config) when is_list(Config) ->
+ %% Make of copy of the data directory.
+ DataDir = ?datadir,
+ PrivDir = ?privdir,
+ ?line CopyDir = fname(PrivDir, "datacopy"),
+ ?line TarFile = fname(PrivDir, "datacopy.tgz"),
+ ?line {ok, Tar} = erl_tar:open(TarFile, [write, compressed]),
+ ?line ok = erl_tar:add(Tar, DataDir, CopyDir, [compressed]),
+ ?line ok = erl_tar:close(Tar),
+ ?line ok = erl_tar:extract(TarFile, [compressed]),
+ ?line ok = file:delete(TarFile),
+
+ %% Compile source files in the copy directory.
+ ?line Sources = filelib:wildcard(fname([CopyDir,'*','*','*','*','*.erl'])),
+ ?line lists:foreach(fun compile_source/1, Sources),
+
+ %% To use in end_per_testcase
+ Path = code:get_path(),
+ {ok,Cwd} = file:get_cwd(),
+
+ [{copy_dir, CopyDir}, {cwd,Cwd}, {path,Path} | Config].
+
+compile_source(File) ->
+ %% The compiler will no longer create a Beam file
+ %% with a module name that does not match the output
+ %% file, so we must compile to a binary and write
+ %% the output file ourselves.
+ U = filename:dirname(filename:dirname(File)),
+ Base = filename:rootname(filename:basename(File)),
+ OutFile = filename:join([U,"ebin",Base++".beam"]),
+ OutFileTemp = OutFile ++ "#",
+ {ok,_,Code} = compile:file(File, [binary]),
+ ok = file:write_file(OutFileTemp, Code),
+ file:rename(OutFileTemp, OutFile).
+
+end_per_suite(Conf) when is_list(Conf) ->
+ %% Nothing.
+ Conf.
+
+init_per_testcase(link_tar, Config) ->
+ case os:type() of
+ {unix, _} -> init_per_testcase(dummy, Config);
+ {win32, _} -> {skip, "Skip on windows"}
+ end;
+init_per_testcase(_Case, Config) ->
+ ?line Dog = test_server:timetrap(?default_timeout),
+ [{watchdog, Dog}|Config].
+
+end_per_testcase(_Case, Config) ->
+ Dog=?config(watchdog, Config),
+ test_server:timetrap_cancel(Dog),
+ case {?config(path,Config),?config(cwd,Config)} of
+ {undefined,undefined} ->
+ ok;
+ {Path,Cwd} ->
+ true = code:set_path(Path),
+ ok = file:set_cwd(Cwd)
+ end,
+ ok.
+
+
+
+%% Usage:
+%% systools:make_script("RelName")
+%% Make a boot file from RelName.rel.
+%% Generates RelName.{script,boot}
+%% systools:make_tar("RelName")
+%% Make a release package from RelName.rel.
+%% Generates RelName.tar,Z
+%% systools:script2boot(File)
+%% File.script -> File.boot
+%% systools:make_relup("Target", ["UpFromRel"...], ["DownToRel"...], Opts)
+%% Gather all appup scripts to the relup file
+%%
+
+
+%% make_script
+%%
+script_options(suite) -> [];
+script_options(doc) ->
+ ["Check illegal script options."];
+script_options(Config) when is_list(Config) ->
+ ?line {'EXIT',{{badarg,[{path,["Path",12,"Another"]}]}, _}} =
+ (catch systools:make_script("release", [{path,["Path",12,"Another"]}])),
+ ?line {'EXIT',{{badarg,[sillent]}, _}} =
+ (catch systools:make_script("release",
+ [{path,["Path","Another"]},sillent])),
+ ?line {'EXIT',{{badarg,[locall]}, _}} =
+ (catch systools:make_script("release",
+ [{path,["Path","Another"]},locall])),
+ ?line {'EXIT',{{badarg,[src_testsxx]}, _}} =
+ (catch systools:make_script("release",
+ [{path,["Path"]},src_testsxx])),
+ ?line {'EXIT',{{badarg,[{variables, {"TEST", "/home/lib"}}]}, _}} =
+ (catch systools:make_script("release",
+ [{variables, {"TEST", "/home/lib"}}])),
+ ?line {'EXIT',{{badarg,[{variables, [{a, b}, {"a", "b"}]}]}, _}} =
+ (catch systools:make_script("release",
+ [{variables, [{a, b}, {"a", "b"}]}])),
+ ?line {'EXIT',{{badarg,[exreff]}, _}} =
+ (catch systools:make_script("release",
+ [{path,["Path","Another"]},exreff])),
+ ?line {'EXIT',{{badarg,[{exref,["appl"]}]}, _}} =
+ (catch systools:make_script("release", [{exref,["appl"]}])),
+ ?line {'EXIT',{{badarg,[{machine, "appl"}]}, _}} =
+ (catch systools:make_script("release", [{machine,"appl"}])),
+ ok.
+
+
+%% make_script
+%%
+normal_script(suite) -> [];
+normal_script(doc) ->
+ ["Check that make_script handles normal case."];
+normal_script(Config) when is_list(Config) ->
+ ?line {ok, OldDir} = file:get_cwd(),
+ ?line PSAVE = code:get_path(), % Save path
+
+ ?line {LatestDir, LatestName} = create_script(latest,Config),
+
+ ?line DataDir = filename:absname(?copydir),
+ ?line LibDir = fname([DataDir, d_normal, lib]),
+ ?line P1 = fname([LibDir, 'db-2.1', ebin]),
+ ?line P2 = fname([LibDir, 'fe-3.1', ebin]),
+
+ ?line true = code:add_patha(P1),
+ ?line true = code:add_patha(P2),
+
+ ?line ok = file:set_cwd(LatestDir),
+
+ ?line ok = systools:make_script(filename:basename(LatestName)),
+ ?line {ok, _} = read_script_file(LatestName), % Check readabillity
+
+ %% Check the same but w. silent flag
+ ?line {ok, _, []} = systools:make_script(LatestName, [silent]),
+
+ %% Use the local option
+ ?line ok = systools:make_script(LatestName, [local]),
+ ?line ok = check_script_path(LatestName),
+
+ %% use the path option
+ ?line code:set_path(PSAVE), % Restore path
+ %% Mess up std path:
+ ?line true = code:add_patha(fname([LibDir, 'db-1.0', ebin])),
+ ?line true = code:add_patha(fname([LibDir, 'fe-2.1', ebin])),
+
+ ?line error = systools:make_script(LatestName), %should fail
+ ?line ok = systools:make_script(LatestName,[{path, [P1, P2]}]),
+
+ ?line ok = file:set_cwd(OldDir),
+ ?line code:set_path(PSAVE), % Restore path
+ ok.
+
+
+%% make_script
+%%
+no_mod_vsn_script(suite) -> [];
+no_mod_vsn_script(doc) ->
+ ["Check that make_script handles normal case.",
+ "Modules specified without version in .app file (db-3.1)."
+ "Note that this is now the normal way - i.e. systools now "
+ "ignores the module versions in the .app file."];
+no_mod_vsn_script(Config) when is_list(Config) ->
+ ?line {ok, OldDir} = file:get_cwd(),
+ ?line PSAVE = code:get_path(), % Save path
+
+ ?line {LatestDir, LatestName} = create_script(latest_no_mod_vsn,Config),
+
+ ?line DataDir = filename:absname(?copydir),
+ ?line LibDir = fname([DataDir, d_normal, lib]),
+ ?line P1 = fname([LibDir, 'db-3.1', ebin]),
+ ?line P2 = fname([LibDir, 'fe-3.1', ebin]),
+
+ ?line true = code:add_patha(P1),
+ ?line true = code:add_patha(P2),
+
+ ?line ok = file:set_cwd(LatestDir),
+
+ ?line ok = systools:make_script(filename:basename(LatestName)),
+ ?line {ok, _} = read_script_file(LatestName), % Check readabillity
+
+ %% Check the same but w. silent flag
+ ?line {ok, _, []} = systools:make_script(LatestName, [silent]),
+
+ %% Use the local option
+ ?line ok = systools:make_script(LatestName, [local]),
+ ?line ok = check_script_path(LatestName),
+
+ %% use the path option
+ ?line code:set_path(PSAVE), % Restore path
+ %% Mess up std path:
+ ?line true = code:add_patha(fname([LibDir, 'db-1.0', ebin])),
+ ?line true = code:add_patha(fname([LibDir, 'fe-2.1', ebin])),
+
+ ?line error = systools:make_script(LatestName), %should fail
+ ?line ok = systools:make_script(LatestName,
+ [{path, [P1, P2]}]),
+
+ ?line ok = file:set_cwd(OldDir),
+ ?line code:set_path(PSAVE), % Restore path
+ ok.
+
+
+%% make_script
+%%
+wildcard_script(suite) -> [];
+wildcard_script(doc) ->
+ ["Check that make_script handles wildcards in path."];
+wildcard_script(Config) when is_list(Config) ->
+ ?line {ok, OldDir} = file:get_cwd(),
+
+ ?line {LatestDir, LatestName} = create_script(latest,Config),
+
+ ?line DataDir = filename:absname(?copydir),
+ ?line LibDir = fname([DataDir, d_normal, lib]),
+ ?line WildDir = fname([LibDir, '*', ebin]),
+
+ ?line ok = file:set_cwd(LatestDir),
+
+ ?line error = systools:make_script(filename:basename(LatestName)),
+
+ ?line ok = systools:make_script(LatestName,
+ [{path, [WildDir]}]),
+
+ ?line {ok, _} = read_script_file(LatestName), % Check readabillity
+
+ ?line ok = file:set_cwd(OldDir),
+ ok.
+
+
+%% make_script
+%%
+variable_script(suite) -> [];
+variable_script(doc) ->
+ ["Add own installation dependent variable in script."];
+variable_script(Config) when is_list(Config) ->
+ ?line {ok, OldDir} = file:get_cwd(),
+
+ ?line {LatestDir, LatestName} = create_script(latest,Config),
+
+ ?line DataDir = filename:absname(?copydir),
+ ?line LibDir = fname([DataDir, d_normal, lib]),
+ ?line P = [fname([LibDir, 'db-2.1', ebin]),
+ fname([LibDir, 'fe-3.1', ebin])],
+
+ ?line ok = file:set_cwd(LatestDir),
+
+ ?line ok = systools:make_script(LatestName,
+ [{path, P},
+ {variables, [{"TEST", LibDir}]}]),
+
+ %% Check variables
+ ?line ok = check_var_script_file([fname(['$TEST', 'db-2.1', ebin]),
+ fname(['$TEST', 'fe-3.1', ebin])],
+ P,
+ LatestName),
+
+ ?line ok = file:set_cwd(OldDir),
+ ok.
+
+%% make_script
+%%
+abnormal_script(suite) -> [];
+abnormal_script(doc) ->
+ ["Abnormal cases."];
+abnormal_script(Config) when is_list(Config) ->
+ ?line {ok, OldDir} = file:get_cwd(),
+
+ ?line {LatestDir, LatestName} = create_script(latest,Config),
+
+ ?line DataDir = filename:absname(?copydir),
+
+ ?line ok = file:set_cwd(LatestDir),
+ ?line LibDir = fname([DataDir, d_bad_app_vsn, lib]),
+ ?line P = [fname([LibDir, 'db-2.1', ebin]),
+ fname([LibDir, 'fe-3.1', ebin])],
+
+ %% Check wrong app vsn
+ ?line error = systools:make_script(LatestName, [{path, P}]),
+ ?line {error,
+ systools_make,
+ [{error_reading, {db, {no_valid_version,
+ {{"should be","2.1"},
+ {"found file", _, "2.0"}}}}}]} =
+ systools:make_script(LatestName, [silent, {path, P}]),
+
+ ?line ok = file:set_cwd(OldDir),
+ ok.
+
+
+%% make_script
+%%
+src_tests_script(suite) -> [];
+src_tests_script(doc) ->
+ ["Do not check date of object file or that source code can be found."];
+src_tests_script(Config) when is_list(Config) ->
+ ?line {ok, OldDir} = file:get_cwd(),
+ ?line PSAVE = code:get_path(), % Save path
+
+ ?line {LatestDir, LatestName} = create_script(latest,Config),
+
+ ?line DataDir = filename:absname(?copydir),
+ ?line LibDir = fname([DataDir, d_missing_src, lib]),
+ ?line P1 = fname([LibDir, 'db-2.1', ebin]),
+ ?line P2 = fname([LibDir, 'fe-3.1', ebin]),
+ N = [P1, P2],
+
+ ?line ok = file:set_cwd(LatestDir),
+
+ %% Manipulate the modification date of a beam file so it seems
+ %% older than its .erl file
+ ?line Erl = filename:join([P1,"..","src","db1.erl"]),
+ ?line {ok, FileInfo=#file_info{mtime={{Y,M,D},T}}} = file:read_file_info(Erl),
+ ?line Beam = filename:join(P1,"db1.beam"),
+ ?line ok=file:write_file_info(Beam, FileInfo#file_info{mtime={{Y-1,M,D},T}}),
+
+ %% Remove a .erl file
+ ?line Erl2 = filename:join([P1,"..","src","db2.erl"]),
+ ?line file:delete(Erl2),
+
+ %% Then make script - two warnings should be issued when
+ %% src_tests is given
+ %% 1. old object code for db1.beam
+ %% 2. missing source code for db2.beam
+ ?line {ok, _, [{warning,{obj_out_of_date,_}},
+ {warning,{source_not_found,_}}]} =
+ systools:make_script(LatestName, [silent, {path, N}, src_tests]),
+
+ %% Without the src_tests option, no warning should be issued
+ ?line {ok, _, []} =
+ systools:make_script(LatestName, [silent, {path, N}]),
+
+ %% Check that the old no_module_tests option (from the time when
+ %% it was default to do the src_test) is ignored
+ ?line {ok, _, [{warning,{obj_out_of_date,_}},
+ {warning,{source_not_found,_}}]} =
+ systools:make_script(LatestName, [silent,
+ {path, N},
+ no_module_tests,
+ src_tests]),
+
+ ?line ok = file:set_cwd(OldDir),
+ ?line code:set_path(PSAVE),
+ ok.
+
+%% make_script
+%%
+warn_shadow_script(suite) -> [];
+warn_shadow_script(doc) ->
+ ["Check that jam file out of date warning doesn't",
+ "shadow bad module version error."];
+warn_shadow_script(Config) when is_list(Config) ->
+ %% This test has been removed since the 'vsn' attribute is
+ %% not used any more, starting with R6. No warning
+ %% 'obj_out_of_date' seemed to be generated.
+ true.
+
+
+%% make_script
+%%
+crazy_script(suite) -> [];
+crazy_script(doc) ->
+ ["Do the crazy cases."];
+crazy_script(Config) when is_list(Config) ->
+ ?line {ok, OldDir} = file:get_cwd(),
+
+ ?line {LatestDir, LatestName} = create_script(latest, Config),
+
+ ?line DataDir = filename:absname(?copydir),
+ ?line LibDir = fname([DataDir, d_normal, lib]),
+ ?line P = [fname([LibDir, 'db-2.1', ebin]),
+ fname([LibDir, 'fe-3.1', ebin])],
+
+ ?line ok = file:set_cwd(LatestDir),
+
+ %% Run with bad path
+ ?line error = systools:make_script(LatestName),
+ ?line {error, _, [{error_reading, _}, {error_reading, _}]} =
+ systools:make_script(LatestName, [silent]),
+
+ %% Run with .rel file lacking kernel
+ ?line {LatestDir2, LatestName2} = create_script(latest_nokernel, Config),
+ ?line ok = file:set_cwd(LatestDir2),
+
+ ?line error = systools:make_script(LatestName2),
+ ?line {error, _, {missing_mandatory_app,[kernel,stdlib]}} =
+ systools:make_script(LatestName2, [silent,{path,P}]),
+
+ ?line ok = file:set_cwd(OldDir),
+ ok.
+
+%% make_script
+%%
+included_script(suite) -> [];
+included_script(doc) ->
+ ["Check that make_script handles generation of script",
+ "for applications with included applications."];
+included_script(Config) when is_list(Config) ->
+ ?line {ok, OldDir} = file:get_cwd(),
+ ?line {LatestDir, LatestName} = create_include_files(inc1, Config),
+ ?line ok = file:set_cwd(LatestDir),
+ ?line ok = systools:make_script(LatestName),
+ ?line ok = check_include_script(LatestName,
+ [t1, t2, t3, t5, t4, t6],
+ [t1, t3, t6]),
+ ?line ok = file:set_cwd(OldDir),
+ ok.
+
+%% make_script
+%%
+included_override_script(suite) -> [];
+included_override_script(doc) ->
+ ["Check that make_script handles generation of script",
+ "for applications with included applications which are override by",
+ "the .rel file."];
+included_override_script(Config) when is_list(Config) ->
+ ?line {ok, OldDir} = file:get_cwd(),
+ ?line {LatestDir, LatestName} = create_include_files(inc2, Config),
+ ?line ok = file:set_cwd(LatestDir),
+ ?line ok = systools:make_script(LatestName),
+ ?line ok = check_include_script(LatestName,
+ [t1, t2, t3, t4, t6, t5],
+ [t1, t3, t6, t5]),
+
+ ?line {_, LatestName1} = create_include_files(inc3, Config),
+ ?line ok = systools:make_script(LatestName1),
+ ?line ok = check_include_script(LatestName1,
+ [t3, t5, t4, t6, t1, t2],
+ [t3, t6, t1, t2]),
+
+ ?line {_, LatestName2} = create_include_files(inc4, Config),
+ ?line ok = systools:make_script(LatestName2),
+ ?line ok = check_include_script(LatestName2,
+ [t3, t4, t6, t5, t1, t2],
+ [t3, t6, t5, t1, t2]),
+
+ ?line {_, LatestName3} = create_include_files(inc5, Config),
+ ?line ok = systools:make_script(LatestName3),
+ ?line ok = check_include_script(LatestName3,
+ [t3, t4, t6, t1, t2],
+ [t3, t6, t1, t2]),
+
+ ?line ok = file:set_cwd(OldDir),
+ ok.
+
+%% make_script
+%%
+included_fail_script(suite) -> [];
+included_fail_script(doc) ->
+ ["Check that make_script handles errors then generating",
+ "script with included applications."];
+included_fail_script(Config) when is_list(Config) ->
+ ?line {ok, OldDir} = file:get_cwd(),
+ ?line {LatestDir, LatestName} = create_include_files(inc6, Config),
+ ?line ok = file:set_cwd(LatestDir),
+ ?line {error, _, {undefined_applications,[t2]}} =
+ systools:make_script(LatestName, [silent]),
+
+ ?line {_, LatestName1} = create_include_files(inc7, Config),
+ ?line {error, _, {duplicate_include,[{{t5,t7,_,_},{t5,t6,_,_}}]}} =
+ systools:make_script(LatestName1, [silent]),
+
+ ?line {_, LatestName3} = create_include_files(inc9, Config),
+ ?line {error, _, {circular_dependencies,[{t10,_},{t8,_}]}} =
+ systools:make_script(LatestName3, [silent]),
+
+ ?line {_, LatestName4} = create_include_files(inc10, Config),
+ ?line {error, _, [{error_reading,{t9,{override_include,[t7]}}}]} =
+ systools:make_script(LatestName4, [silent]),
+
+ ?line ok = file:set_cwd(OldDir),
+ ok.
+
+%% make_script
+%%
+included_bug_script(suite) -> [];
+included_bug_script(doc) ->
+ ["Check that make_script handles generation of script",
+ "with difficult dependency for included applications."];
+included_bug_script(Config) when is_list(Config) ->
+ ?line {ok, OldDir} = file:get_cwd(),
+ ?line {LatestDir, LatestName} = create_include_files(inc11, Config),
+ ?line ok = file:set_cwd(LatestDir),
+ ?line ok = systools:make_script(LatestName),
+ ?line ok = check_include_script(LatestName,
+ [t13, t11, t12],
+ [t11, t12]),
+ ?line ok = file:set_cwd(OldDir),
+ ok.
+
+
+%% make_script
+%%
+otp_3065(suite) -> [];
+otp_3065(doc) ->
+ ["Circular dependencies in systools:make_script()."];
+otp_3065(Config) when is_list(Config) ->
+ ?line {ok, OldDir} = file:get_cwd(),
+ ?line {LatestDir, LatestName} = create_include_files(otp_3065, Config),
+ ?line ok = file:set_cwd(LatestDir),
+ ?line ok = systools:make_script(LatestName),
+ ?line ok = check_include_script(LatestName,
+ [aa12, chAts, chTraffic],
+ [chTraffic]),
+ ?line ok = file:set_cwd(OldDir),
+ ok.
+
+
+%% make_script
+%%
+exref_script(suite) -> [];
+exref_script(doc) ->
+ ["Check that make_script exref option works."];
+exref_script(Config) when is_list(Config) ->
+ ?line {ok, OldDir} = file:get_cwd(),
+ ?line PSAVE = code:get_path(), % Save path
+
+ ?line {LatestDir, LatestName} = create_script(latest,Config),
+
+ ?line DataDir = filename:absname(?copydir),
+ ?line LibDir = fname([DataDir, d_normal, lib]),
+ ?line P = [fname([LibDir, 'db-2.1', ebin]),
+ fname([LibDir, 'fe-3.1', ebin])],
+
+ ?line ok = file:set_cwd(LatestDir),
+
+ ?line {ok, _, _} = systools:make_script(LatestName, [{path,P}, silent]),
+
+ %% Complete exref
+ ?line {ok, _, W1} =
+ systools:make_script(LatestName, [exref, {path,P}, silent]),
+ ?line check_exref_warnings(with_db1, W1),
+ ?line {ok, _} = read_script_file(LatestName), % Check readabillity
+
+ %% Only exref the db application.
+ ?line {ok, _, W2} =
+ systools:make_script(LatestName, [{exref,[db]}, {path,P}, silent]),
+ ?line check_exref_warnings(with_db1, W2),
+ ?line {ok, _} = read_script_file(LatestName), % Check readabillity
+
+ %% Only exref the fe application.
+ ?line {ok, _, W3} =
+ systools:make_script(LatestName, [{exref,[fe]}, {path,P}, silent]),
+ ?line check_exref_warnings(without_db1, W3),
+ ?line {ok, _} = read_script_file(LatestName), % Check readabillity
+
+ %% exref the db and stdlib applications.
+ ?line {ok, _, W4} =
+ systools:make_script(LatestName, [{exref,[db,stdlib]}, {path,P}, silent]),
+ ?line check_exref_warnings(with_db1, W4),
+ ?line {ok, _} = read_script_file(LatestName), % Check readabillity
+ ?line ok = file:set_cwd(OldDir),
+ ?line code:set_path(PSAVE), % Restore path
+ ok.
+
+check_exref_warnings(with_db1, W) ->
+ case get_exref(undef, W) of
+ {ok, [{db2,non_existing_func,0},
+ {fe2,non_existing_func,0},
+ {lists,non_existing_func,1}]} ->
+ ok;
+ {ok, L} ->
+ test_server:fail({exref_warning_undef, L});
+ _E ->
+ test_server:fail({bad_undef,_E})
+ end;
+check_exref_warnings(without_db1, W) ->
+ case get_exref(undef, W) of
+ false ->
+ ok;
+ {ok, L} ->
+ test_server:fail({exref_warning_undef, L})
+ end.
+
+get_exref(undef, W) -> filter(no_hipe(get_exref1(exref_undef, W))).
+
+filter(false) ->
+ false;
+filter({ok, W}) ->
+ {ok, filter(W)};
+filter(L) ->
+ lists:filter(fun%({hipe_consttab,_,_}) -> false;
+ ({int,_,_}) -> false;
+ ({i,_,_}) -> false;
+ ({crypto,_,_}) -> false;
+ (_) -> true
+ end,
+ L).
+
+get_exref1(T, [{warning, {T, Value}}|_]) -> {ok, Value};
+get_exref1(T, [_|W]) -> get_exref1(T, W);
+get_exref1(_, []) -> false.
+
+no_hipe(false) ->
+ false;
+no_hipe({ok, Value}) ->
+ case erlang:system_info(hipe_architecture) of
+ undefined ->
+ Hipe = "hipe",
+ Fun = fun({M,_,_}) -> not lists:prefix(Hipe, atom_to_list(M)) end,
+ NewValue = lists:filter(Fun, Value),
+ {ok, NewValue};
+ _Arch ->
+ {ok, Value}
+ end.
+
+%% tar_options
+%%
+tar_options(suite) -> [];
+tar_options(doc) ->
+ ["Check illegal tar options."];
+tar_options(Config) when is_list(Config) ->
+ ?line {'EXIT',{{badarg,[{path,["Path",12,"Another"]}]}, _}} =
+ (catch systools:make_tar("release", [{path,["Path",12,"Another"]}])),
+ ?line {'EXIT',{{badarg,[sillent]}, _}} =
+ (catch systools:make_tar("release",
+ [{path,["Path","Another"]},sillent])),
+ ?line {'EXIT',{{badarg,[{dirs,["dirs"]}]}, _}} =
+ (catch systools:make_tar("release", [{dirs, ["dirs"]}])),
+ ?line {'EXIT',{{badarg,[{erts, illegal}]}, _}} =
+ (catch systools:make_tar("release", [{erts, illegal}])),
+ ?line {'EXIT',{{badarg,[src_testsxx]}, _}} =
+ (catch systools:make_tar("release",
+ [{path,["Path"]},src_testsxx])),
+ ?line {'EXIT',{{badarg,[{variables, [{a, b}, {"a", "b"}]}]}, _}} =
+ (catch systools:make_tar("release",
+ [{variables, [{a, b}, {"a", "b"}]}])),
+ ?line {'EXIT',{{badarg,[{var_tar, illegal}]}, _}} =
+ (catch systools:make_tar("release", [{var_tar, illegal}])),
+ ?line {'EXIT',{{badarg,[exreff]}, _}} =
+ (catch systools:make_tar("release",
+ [{path,["Path","Another"]},exreff])),
+ ?line {'EXIT',{{badarg,[{exref,["appl"]}]}, _}} =
+ (catch systools:make_tar("release", [{exref,["appl"]}])),
+ ?line {'EXIT',{{badarg,[{machine, "appl"}]}, _}} =
+ (catch systools:make_tar("release", [{machine,"appl"}])),
+ ok.
+
+
+%% normal_tar
+%%
+normal_tar(suite) -> [];
+normal_tar(doc) ->
+ ["Check normal case"];
+normal_tar(Config) when is_list(Config) ->
+ ?line {ok, OldDir} = file:get_cwd(),
+
+ ?line {LatestDir, LatestName} = create_script(latest,Config),
+
+ ?line DataDir = filename:absname(?copydir),
+ ?line LibDir = fname([DataDir, d_normal, lib]),
+ ?line P = [fname([LibDir, 'db-2.1', ebin]),
+ fname([LibDir, 'fe-3.1', ebin])],
+
+ ?line ok = file:set_cwd(LatestDir),
+
+ ?line {ok, _, _} = systools:make_script(LatestName, [silent, {path, P}]),
+ ?line ok = systools:make_tar(LatestName, [{path, P}]),
+ ?line ok = check_tar(fname([lib,'db-2.1',ebin,'db.app']), LatestName),
+ ?line {ok, _, _} = systools:make_tar(LatestName, [{path, P}, silent]),
+ ?line ok = check_tar(fname([lib,'fe-3.1',ebin,'fe.app']), LatestName),
+
+ ?line ok = file:set_cwd(OldDir),
+ ok.
+
+%% no_mod_vsn_tar
+%%
+no_mod_vsn_tar(suite) -> [];
+no_mod_vsn_tar(doc) ->
+ ["Check normal case",
+ "Modules specified without version in .app file (db-3.1)."
+ "Note that this is now the normal way - i.e. systools now "
+ "ignores the module versions in the .app file."];
+no_mod_vsn_tar(Config) when is_list(Config) ->
+ ?line {ok, OldDir} = file:get_cwd(),
+
+ ?line {LatestDir, LatestName} = create_script(latest_no_mod_vsn,Config),
+
+ ?line DataDir = filename:absname(?copydir),
+ ?line LibDir = fname([DataDir, d_normal, lib]),
+ ?line P = [fname([LibDir, 'db-3.1', ebin]),
+ fname([LibDir, 'fe-3.1', ebin])],
+
+ ?line ok = file:set_cwd(LatestDir),
+
+ ?line {ok, _, _} = systools:make_script(LatestName, [silent, {path, P}]),
+ ?line ok = systools:make_tar(LatestName, [{path, P}]),
+ ?line ok = check_tar(fname([lib,'db-3.1',ebin,'db.app']), LatestName),
+ ?line {ok, _, _} = systools:make_tar(LatestName, [{path, P}, silent]),
+ ?line ok = check_tar(fname([lib,'fe-3.1',ebin,'fe.app']), LatestName),
+
+ ?line ok = file:set_cwd(OldDir),
+ ok.
+
+%% variable_tar
+%%
+variable_tar(suite) -> [];
+variable_tar(doc) ->
+ ["Use variable and create separate tar (included in generated tar)."];
+variable_tar(Config) when is_list(Config) ->
+ ?line {ok, OldDir} = file:get_cwd(),
+
+ ?line {LatestDir, LatestName} = create_script(latest,Config),
+
+ ?line DataDir = filename:absname(?copydir),
+ ?line LibDir = fname([DataDir, d_normal, lib]),
+ ?line P = [fname([LibDir, 'db-2.1', ebin]),
+ fname([LibDir, 'fe-3.1', ebin])],
+
+ ?line ok = file:set_cwd(LatestDir),
+
+ ?line {ok, _, _} = systools:make_script(LatestName,
+ [silent,
+ {path, P},
+ {variables,[{"TEST", LibDir}]}]),
+
+ ?line ok = systools:make_tar(LatestName, [{path, P},
+ {variables,[{"TEST", LibDir}]}]),
+ ?line ok = check_var_tar("TEST", LatestName),
+
+ ?line {ok, _, _} = systools:make_tar(LatestName,
+ [{path, P}, silent,
+ {variables,[{"TEST", LibDir}]}]),
+ ?line ok = check_var_tar("TEST", LatestName),
+
+ ?line ok = file:set_cwd(OldDir),
+ ok.
+
+%% link_tar
+%%
+link_tar(suite) -> [];
+link_tar(doc) ->
+ ["Check that symlinks in applications are handled correctly"];
+link_tar(Config) when is_list(Config) ->
+ ?line {ok, OldDir} = file:get_cwd(),
+
+ ?line {LatestDir, LatestName} = create_script(latest,Config),
+
+ ?line DataDir = filename:absname(?copydir),
+ ?line LibDir = fname([DataDir, d_links, lib]),
+ ?line P = [fname([LibDir, 'db-2.1', ebin]),
+ fname([LibDir, 'fe-3.1', ebin])],
+
+ %% Make some links
+ ?line Db1Erl = fname(['db-2.1',src,'db1.erl']),
+ ?line NormalDb1Erl = fname([DataDir,d_normal,lib,Db1Erl]),
+ ?line LinkDb1Erl = fname([LibDir, Db1Erl]),
+ ?line ok = file:make_symlink(NormalDb1Erl, LinkDb1Erl),
+ ?line Db1Beam = fname(['db-2.1',ebin,'db1.beam']),
+ ?line NormalDb1Beam = fname([DataDir,d_normal,lib,Db1Beam]),
+ ?line LinkDb1Beam = fname([LibDir, Db1Beam]),
+ ?line ok = file:make_symlink(NormalDb1Beam, LinkDb1Beam),
+ ?line FeApp = fname(['fe-3.1',ebin,'fe.app']),
+ ?line NormalFeApp = fname([DataDir,d_normal,lib,FeApp]),
+ ?line LinkFeApp = fname([LibDir, FeApp]),
+ ?line ok = file:make_symlink(NormalFeApp, LinkFeApp),
+
+ %% Create the tar and check that the linked files are included as
+ %% regular files
+ ?line ok = file:set_cwd(LatestDir),
+
+ ?line {ok,_,[]} = systools:make_script(LatestName, [{path, P},silent]),
+
+ ?line {ok, _, []} = systools:make_tar(LatestName, [{path, P}, silent]),
+ ?line ok = check_tar_regular(?privdir,
+ [fname([lib,FeApp]),
+ fname([lib,Db1Beam])],
+ LatestName),
+
+ ?line {ok, _, []} = systools:make_tar(LatestName, [{path, P}, silent,
+ {dirs, [src]}]),
+ ?line ok = check_tar_regular(?privdir,
+ [fname([lib,FeApp]),
+ fname([lib,Db1Beam]),
+ fname([lib,Db1Erl])],
+ LatestName),
+
+ ?line ok = file:set_cwd(OldDir),
+ ok.
+
+%% src_tests_tar
+%%
+src_tests_tar(suite) -> [];
+src_tests_tar(doc) ->
+ ["Do not check date of object file or that source code can be found."];
+src_tests_tar(Config) when is_list(Config) ->
+ ?line {ok, OldDir} = file:get_cwd(),
+
+ ?line {LatestDir, LatestName} = create_script(latest,Config),
+
+ ?line DataDir = filename:absname(?copydir),
+ ?line LibDir = fname([DataDir, d_missing_src, lib]),
+ ?line P1 = fname([LibDir, 'db-2.1', ebin]),
+ ?line P2 = fname([LibDir, 'fe-3.1', ebin]),
+ P = [P1, P2],
+
+ ?line ok = file:set_cwd(LatestDir),
+
+ %% Manipulate the modification date of a beam file so it seems
+ %% older than the .erl file
+ Erl = filename:join([P1,"..","src","db1.erl"]),
+ {ok, FileInfo=#file_info{mtime={{Y,M,D},T}}} = file:read_file_info(Erl),
+ Beam = filename:join(P1,"db1.beam"),
+ ok = file:write_file_info(Beam, FileInfo#file_info{mtime={{Y-1,M,D},T}}),
+
+ %% Remove a .erl file
+ ?line Erl2 = filename:join([P1,"..","src","db2.erl"]),
+ ?line file:delete(Erl2),
+
+ ?line ok = systools:make_script(LatestName, [{path, P}]),
+
+ %% Then make tar - two warnings should be issued when
+ %% src_tests is given
+ %% 1. old object code for db1.beam
+ %% 2. missing source code for db2.beam
+ ?line {ok, _, [{warning,{obj_out_of_date,_}},
+ {warning,{source_not_found,_}}]} =
+ systools:make_tar(LatestName, [{path, P}, silent,
+ {dirs, [src]},
+ src_tests]),
+ ?line ok = check_tar(fname([lib,'db-2.1',src,'db1.erl']), LatestName),
+
+ %% Without the src_tests option, no warning should be issued
+ ?line {ok, _, []} = systools:make_tar(LatestName, [{path, P}, silent,
+ {dirs, [src]}]),
+ ?line ok = check_tar(fname([lib,'db-2.1',src,'db1.erl']), LatestName),
+
+ %% Check that the old no_module_tests option (from the time when
+ %% it was default to do the src_test) is ignored
+ ?line {ok, _, [{warning,{obj_out_of_date,_}},
+ {warning,{source_not_found,_}}]} =
+ systools:make_tar(LatestName, [{path, P}, silent,
+ {dirs, [src]},
+ no_module_tests,
+ src_tests]),
+ ?line ok = check_tar(fname([lib,'db-2.1',src,'db1.erl']), LatestName),
+
+ ?line ok = file:set_cwd(OldDir),
+ ok.
+
+%% shadow_tar
+%%
+shadow_tar(suite) -> [];
+shadow_tar(doc) ->
+ ["Check that jam file out of date warning doesn't",
+ "shadow bad module version error."];
+shadow_tar(Config) when is_list(Config) ->
+ % This test has been commented out since the 'vsn' attribute is not used
+ % any more, starting with R6. No warning 'obj_out_of_date' seemed to be
+ % generated.
+ true;
+shadow_tar(Config) when is_list(Config) ->
+ ?line {ok, OldDir} = file:get_cwd(),
+ ?line PSAVE = code:get_path(), % Save path
+
+ ?line {LatestDir, LatestName} = create_script(latest,Config),
+
+ ?line DataDir = filename:absname(?copydir),
+ ?line LibDir = fname([DataDir, 'd_bad_mod+warn', lib]),
+ ?line P = [fname([LibDir, 'db-2.1', ebin]),
+ fname([LibDir, 'fe-3.1', ebin])],
+
+ ?line ok = file:set_cwd(LatestDir),
+
+ ?line {error, _, _} = systools:make_tar(LatestName, [{path, P}, silent]),
+ ?line {error, _, _} = systools:make_tar(LatestName, [{path, P}, silent,
+ {dirs, [src]}]),
+ ?line ok = file:set_cwd(OldDir),
+ ?line code:set_path(PSAVE),
+ ok.
+
+
+%% var_tar
+%%
+var_tar(suite) -> [];
+var_tar(doc) ->
+ ["Check that make_tar handles generation and placement of tar",
+ "files for variables outside the main tar file.",
+ "Test the {var_tar, include | ownfile | omit} option."];
+var_tar(Config) when is_list(Config) ->
+ ?line {ok, OldDir} = file:get_cwd(),
+ ?line PSAVE = code:get_path(), % Save path
+
+ ?line {LatestDir, LatestName} = create_script(latest,Config),
+
+ ?line DataDir = filename:absname(?copydir),
+ ?line LibDir = fname([DataDir, d_normal, lib]),
+ ?line P = [fname([LibDir, 'db-2.1', ebin]),
+ fname([LibDir, 'fe-3.1', ebin])],
+
+ ?line ok = file:set_cwd(LatestDir),
+
+ ?line {ok, _, _} = systools:make_script(LatestName,
+ [silent,
+ {path, P},
+ {variables,[{"TEST", LibDir}]}]),
+
+ ?line ok = systools:make_tar(LatestName, [{path, P},
+ {var_tar, ownfile},
+ {variables,[{"TEST", LibDir}]}]),
+
+ ?line true = exists_tar_file("TEST"), %% Also removes the file !
+ ?line {error, {not_generated, _}} = check_var_tar("TEST", LatestName),
+
+ ?line ok = systools:make_tar(LatestName, [{path, P},
+ {var_tar, omit},
+ {variables,[{"TEST", LibDir}]}]),
+
+ ?line {error, {not_generated, _}} = check_var_tar("TEST", LatestName),
+ ?line false = exists_tar_file("TEST"),
+
+ ?line ok = systools:make_tar(LatestName, [{path, P},
+ {var_tar, include},
+ {variables,[{"TEST", LibDir}]}]),
+
+ ?line ok = check_var_tar("TEST", LatestName),
+ ?line false = exists_tar_file("TEST"),
+
+ ?line ok = file:set_cwd(OldDir),
+ ?line code:set_path(PSAVE),
+ ok.
+
+
+%% exref_tar
+%%
+exref_tar(suite) -> [];
+exref_tar(doc) ->
+ ["Check exref option."];
+exref_tar(Config) when is_list(Config) ->
+ ?line {ok, OldDir} = file:get_cwd(),
+
+ ?line {LatestDir, LatestName} = create_script(latest,Config),
+
+ ?line DataDir = filename:absname(?copydir),
+ ?line LibDir = fname([DataDir, d_normal, lib]),
+ ?line P = [fname([LibDir, 'db-2.1', ebin]),
+ fname([LibDir, 'fe-3.1', ebin])],
+
+ ?line ok = file:set_cwd(LatestDir),
+
+ ?line {ok, _, _} = systools:make_script(LatestName, [silent, {path, P}]),
+
+ %% Complete exref
+ ?line {ok, _, W1} =
+ systools:make_tar(LatestName, [exref, {path, P}, silent]),
+ ?line check_exref_warnings(with_db1, W1),
+ ?line ok = check_tar(fname([lib,'db-2.1',ebin,'db.app']), LatestName),
+
+ %% Only exref the db application.
+ ?line {ok, _, W2} =
+ systools:make_tar(LatestName, [{exref, [db]}, {path, P}, silent]),
+ ?line check_exref_warnings(with_db1, W2),
+ ?line ok = check_tar(fname([lib,'fe-3.1',ebin,'fe.app']), LatestName),
+
+ %% Only exref the fe application.
+ ?line {ok, _, W3} =
+ systools:make_tar(LatestName, [{exref, [fe]}, {path, P}, silent]),
+ ?line check_exref_warnings(without_db1, W3),
+ ?line ok = check_tar(fname([lib,'db-2.1',ebin,'db.app']), LatestName),
+
+ %% exref the db and stdlib applications.
+ ?line {ok, _, W4} =
+ systools:make_tar(LatestName, [{exref, [db, stdlib]},
+ {path, P}, silent]),
+ ?line check_exref_warnings(with_db1, W4),
+ ?line ok = check_tar(fname([lib,'fe-3.1',ebin,'fe.app']), LatestName),
+
+ ?line ok = file:set_cwd(OldDir),
+ ok.
+
+%% The relup stuff.
+%%
+%%
+
+
+%% make_relup
+%%
+normal_relup(suite) -> [];
+normal_relup(doc) ->
+ ["Check normal case"];
+normal_relup(Config) when is_list(Config) ->
+ ?line {ok, OldDir} = file:get_cwd(),
+
+ ?line {LatestDir,LatestName} = create_script(latest0,Config),
+ ?line {_LatestDir1,LatestName1} = create_script(latest1,Config),
+ ?line {_LatestDir2,LatestName2} = create_script(latest2,Config),
+
+ ?line DataDir = filename:absname(?copydir),
+ ?line LibDir = [fname([DataDir, d_normal, lib])],
+ ?line P = [fname([LibDir, '*', ebin]),
+ fname([DataDir, lib, kernel, ebin]),
+ fname([DataDir, lib, stdlib, ebin])],
+
+ ?line ok = file:set_cwd(LatestDir),
+
+ %% OTP-2561: Check that the option 'restart_emulator' generates a
+ %% "restart_new_emulator" instruction.
+ ?line {ok, _ , _, []} =
+ systools:make_relup(LatestName, [LatestName1], [LatestName1],
+ [{path, P},restart_emulator,silent]),
+ ?line ok = check_relup([{db, "2.1"}], [{db, "1.0"}]),
+ ?line ok = check_restart_emulator(),
+
+ %% This is the ultra normal case
+ ?line ok = systools:make_relup(LatestName, [LatestName1], [LatestName1],
+ [{path, P}]),
+ ?line ok = check_relup([{db, "2.1"}], [{db, "1.0"}]),
+ ?line {ok, _, _, []} =
+ systools:make_relup(LatestName, [LatestName1], [LatestName1],
+ [{path, P}, silent]),
+ ?line ok = check_relup([{db, "2.1"}], [{db, "1.0"}]),
+
+ %% Check that warnings get through
+ ?line ok = systools:make_relup(LatestName, [LatestName2], [LatestName1],
+ [{path, P}]),
+ ?line ok = check_relup([{fe, "3.1"}, {db, "2.1"}], [{db, "1.0"}]),
+ ?line {ok, _, _, [{erts_vsn_changed, _}]} =
+ systools:make_relup(LatestName, [LatestName2], [LatestName1],
+ [{path, P}, silent]),
+ ?line ok = check_relup([{fe, "3.1"}, {db, "2.1"}], [{db, "1.0"}]),
+
+ ?line ok = file:set_cwd(OldDir),
+ ok.
+
+
+%% This test fails if wrong version numbers are seen in the relup file
+%% or if any application is missing. This was triggered by OTP-1360.
+check_relup(UpVsnL, DnVsnL) ->
+ {ok, [{_V1, [{_, _, Up}], [{_, _, Dn}]}]} = file:consult(relup),
+ [] = foldl(fun(X, Acc) ->
+ true = lists:member(X, Acc),
+ lists:delete(X, Acc) end,
+ UpVsnL,
+ [{App, Vsn} || {load_object_code,{App,Vsn,_}} <- Up]),
+ [] = foldl(fun(X, Acc) ->
+ true = lists:member(X, Acc),
+ lists:delete(X, Acc) end,
+ DnVsnL,
+ [{App, Vsn} || {load_object_code,{App,Vsn,_}} <- Dn]),
+ ok.
+
+check_restart_emulator() ->
+ {ok, [{_V1, [{_, _, Up}], [{_, _, Dn}]}]} = file:consult(relup),
+ restart_new_emulator = lists:last(Up),
+ restart_new_emulator = lists:last(Dn),
+ ok.
+
+%% make_relup
+%%
+no_appup_relup(suite) -> [];
+no_appup_relup(doc) ->
+ ["Check that appup files may be missing, but only if we don't need them."];
+no_appup_relup(Config) when is_list(Config) ->
+ ?line {ok, OldDir} = file:get_cwd(),
+
+ ?line {LatestDir,LatestName} = create_script(latest_small,Config),
+ ?line {_LatestDir0,LatestName0} = create_script(latest_small0,Config),
+ ?line {_LatestDir1,LatestName1} = create_script(latest_small1,Config),
+
+ ?line DataDir = filename:absname(?copydir),
+ ?line P1 = [fname([DataDir, d_no_appup, lib, 'fe-3.1', ebin]),
+ fname([DataDir, lib, kernel, ebin]),
+ fname([DataDir, lib, stdlib, ebin])],
+
+ ?line ok = file:set_cwd(LatestDir),
+
+ %% Check that appup might be missing
+ ?line ok =
+ systools:make_relup(LatestName, [LatestName], [], [{path, P1}]),
+ ?line {ok,_, _, []} =
+ systools:make_relup(LatestName, [LatestName], [],
+ [silent, {path, P1}]),
+
+ %% Check that appup might NOT be missing when we need it
+ ?line error =
+ systools:make_relup(LatestName, [LatestName0], [], [{path, P1}]),
+ ?line {error,_,{file_problem, {_,{error,{open,_,_}}}}} =
+ systools:make_relup(LatestName, [], [LatestName0],
+ [silent, {path, P1}]),
+
+ %% Check that appups missing vsn traps
+ ?line P2 = [fname([DataDir, d_no_appup, lib, 'fe-2.1', ebin]),
+ fname([DataDir, lib, kernel, ebin]),
+ fname([DataDir, lib, stdlib, ebin])],
+
+ ?line error =
+ systools:make_relup(LatestName0, [LatestName1], [], [{path, P2}]),
+ ?line {error,_,{no_relup, _, _, _}} =
+ systools:make_relup(LatestName0, [], [LatestName1],
+ [silent, {path, P2}]),
+
+ ?line ok = file:set_cwd(OldDir),
+ ok.
+
+%% make_relup
+%%
+bad_appup_relup(suite) -> [];
+bad_appup_relup(doc) ->
+ ["Check that badly written appup files are detected"];
+bad_appup_relup(Config) when is_list(Config) ->
+ ?line {ok, OldDir} = file:get_cwd(),
+
+ ?line {LatestDir,LatestName} = create_script(latest_small,Config),
+ ?line {_LatestDir0,LatestName0} = create_script(latest_small0,Config),
+
+ ?line DataDir = filename:absname(?copydir),
+ ?line N2 = [fname([DataDir, d_bad_appup, lib, 'fe-3.1', ebin]),
+ fname([DataDir, lib, kernel, ebin]),
+ fname([DataDir, lib, stdlib, ebin])],
+
+ ?line ok = file:set_cwd(LatestDir),
+
+ %% Check that bad appup is trapped
+ ?line error =
+ systools:make_relup(LatestName, [LatestName0], [], [{path, N2}]),
+ ?line {error,_,{file_problem, {_, {error, {parse,_, _}}}}} =
+ systools:make_relup(LatestName, [], [LatestName0],
+ [silent, {path, N2}]),
+
+ ?line ok = file:set_cwd(OldDir),
+ ok.
+
+%% make_relup
+%%
+abnormal_relup(suite) -> [];
+abnormal_relup(doc) ->
+ ["Check some abnormal cases"];
+abnormal_relup(Config) when is_list(Config) ->
+ ?line {ok, OldDir} = file:get_cwd(),
+
+ ?line {LatestDir,LatestName} = create_script(latest0,Config),
+ ?line {_LatestDir1,LatestName1} = create_script(latest1,Config),
+
+ %% Check wrong app vsn
+ ?line DataDir = filename:absname(?copydir),
+ ?line P = [fname([DataDir, d_bad_app_vsn, lib, 'db-2.1', ebin]),
+ fname([DataDir, d_bad_app_vsn, lib, 'fe-3.1', ebin]),
+ fname([DataDir, lib, kernel, ebin]),
+ fname([DataDir, lib, stdlib, ebin])],
+
+ ?line ok = file:set_cwd(LatestDir),
+
+ ?line error = systools:make_relup(LatestName, [LatestName1], [LatestName1],
+ [{path, P}]),
+ ?line R0 = systools:make_relup(LatestName, [LatestName1], [LatestName1],
+ [silent, {path, P}]),
+ ?line {error,systools_make,
+ [{error_reading,{db,{no_valid_version,
+ {{"should be","2.1"},
+ {"found file", _, "2.0"}}}}}]} = R0,
+ ?line ok = file:set_cwd(OldDir),
+ ok.
+
+
+%% Check that application start type is used in relup
+app_start_type_relup(suite) ->
+ [];
+app_start_type_relup(doc) ->
+ ["Release upgrade file with various application start types"];
+app_start_type_relup(Config) when is_list(Config) ->
+ ?line PrivDir = ?config(priv_dir, Config),
+ ?line {Dir1,Name1} = create_script(latest_app_start_type1,Config),
+ ?line {Dir2,Name2} = create_script(latest_app_start_type2,Config),
+ ?line Release1 = filename:join(Dir1,Name1),
+ ?line Release2 = filename:join(Dir2,Name2),
+
+ ?line {ok, Release2Relup, systools_relup, []} = systools:make_relup(Release2, [Release1], [Release1], [{outdir, PrivDir}, silent]),
+ ?line {"2", [{"1",[], UpInstructions}], [{"1",[], DownInstructions}]} = Release2Relup,
+ %% ?t:format("Up: ~p",[UpInstructions]),
+ %% ?t:format("Dn: ~p",[DownInstructions]),
+ ?line [{load_object_code, {mnesia, _, _}},
+ {load_object_code, {sasl, _, _}},
+ {load_object_code, {webtool, _, _}},
+ {load_object_code, {snmp, _, _}},
+ {load_object_code, {xmerl, _, _}},
+ point_of_no_return
+ | UpInstructionsT] = UpInstructions,
+ ?line true = lists:member({apply,{application,start,[mnesia,permanent]}}, UpInstructionsT),
+ ?line true = lists:member({apply,{application,start,[sasl,transient]}}, UpInstructionsT),
+ ?line true = lists:member({apply,{application,start,[webtool,temporary]}}, UpInstructionsT),
+ ?line true = lists:member({apply,{application,load,[snmp]}}, UpInstructionsT),
+ ?line false = lists:any(fun({apply,{application,_,[xmerl|_]}}) -> true; (_) -> false end, UpInstructionsT),
+ ?line [point_of_no_return | DownInstructionsT] = DownInstructions,
+ ?line true = lists:member({apply,{application,stop,[mnesia]}}, DownInstructionsT),
+ ?line true = lists:member({apply,{application,stop,[sasl]}}, DownInstructionsT),
+ ?line true = lists:member({apply,{application,stop,[webtool]}}, DownInstructionsT),
+ ?line true = lists:member({apply,{application,stop,[snmp]}}, DownInstructionsT),
+ ?line true = lists:member({apply,{application,stop,[xmerl]}}, DownInstructionsT),
+ ?line true = lists:member({apply,{application,unload,[mnesia]}}, DownInstructionsT),
+ ?line true = lists:member({apply,{application,unload,[sasl]}}, DownInstructionsT),
+ ?line true = lists:member({apply,{application,unload,[webtool]}}, DownInstructionsT),
+ ?line true = lists:member({apply,{application,unload,[snmp]}}, DownInstructionsT),
+ ?line true = lists:member({apply,{application,unload,[xmerl]}}, DownInstructionsT),
+ ok.
+
+
+otp_6226(suite) ->
+ [];
+otp_6226(doc) ->
+ ["{outdir,Dir} option for systools:make_script()"];
+otp_6226(Config) when is_list(Config) ->
+ PrivDir = ?privdir,
+ ?line {ok, OldDir} = file:get_cwd(),
+
+ ?line {LatestDir, LatestName} = create_script(latest0,Config),
+ ?line {_LatestDir, LatestName1} = create_script(latest1,Config),
+
+ ?line DataDir = filename:absname(?copydir),
+ ?line LibDir = fname([DataDir, d_normal, lib]),
+ ?line P = [fname([LibDir, 'db-2.1', ebin]),
+ fname([LibDir, 'db-1.0', ebin]),
+ fname([LibDir, 'fe-3.1', ebin]),
+ fname([DataDir, lib, kernel, ebin]),
+ fname([DataDir, lib, stdlib, ebin])],
+
+ ?line ok = file:set_cwd(LatestDir),
+
+
+ %% Create an outdir1 directory
+ ?line ok = file:make_dir("outdir1"),
+
+ %% ==== Now test systools:make_script ====
+ %% a) badarg
+ ?line {'EXIT', {{badarg,[{outdir,outdir1}]}, _}} =
+ (catch systools:make_script(LatestName, [{outdir,outdir1},
+ {path,P},silent])),
+
+ %% b) absolute path
+ Outdir1 = filename:join(PrivDir, "outdir1"),
+ ?line {ok,_,[]} = systools:make_script(LatestName, [{outdir,Outdir1},
+ {path,P},silent]),
+ ?line Script1 = filename:join(Outdir1, LatestName ++ ".script"),
+ ?line Boot1 = filename:join(Outdir1, LatestName ++ ".boot"),
+ ?line true = filelib:is_file(Script1),
+ ?line true = filelib:is_file(Boot1),
+ ?line ok = file:delete(Script1),
+ ?line ok = file:delete(Boot1),
+
+ %% c) relative path
+ ?line {ok,_,[]} = systools:make_script(LatestName, [{outdir,"./outdir1"},
+ {path,P},silent]),
+ ?line true = filelib:is_file(Script1),
+ ?line true = filelib:is_file(Boot1),
+ ?line ok = file:delete(Script1),
+ ?line ok = file:delete(Boot1),
+
+ %% d) absolute but incorrect path
+ ?line Outdir2 = filename:join(PrivDir, "outdir2"),
+ ?line Script2 = filename:join(Outdir2, LatestName ++ ".script"),
+ ?line {error,_,{open,Script2,_}} =
+ systools:make_script(LatestName, [{outdir,Outdir2},{path,P},silent]),
+
+ %% e) relative but incorrect path
+ ?line {error,_,{open,_,_}} =
+ systools:make_script(LatestName, [{outdir,"./outdir2"},{path,P},silent]),
+
+ %% f) with .rel in another directory than cwd
+ ?line ok = file:set_cwd(Outdir1),
+ ?line {ok,_,[]} = systools:make_script(filename:join(PrivDir, LatestName),
+ [{outdir,"."},{path,P},silent]),
+ ?line true = filelib:is_file(LatestName ++ ".script"),
+ ?line true = filelib:is_file(LatestName ++ ".boot"),
+ ?line ok = file:delete(LatestName ++ ".script"),
+ ?line ok = file:delete(LatestName ++ ".boot"),
+ ?line ok = file:set_cwd(LatestDir),
+
+ %% ==== Now test systools:make_tar =====
+ ?line {ok,_,[]} = systools:make_script(LatestName, [{path,P},silent]),
+ %% a) badarg
+ ?line {'EXIT', {{badarg, [{outdir,outdir1}]}, _}} =
+ (catch systools:make_tar(LatestName,[{outdir,outdir1},{path,P},silent])),
+
+ %% b) absolute path
+ ?line {ok,_,[]} = systools:make_tar(LatestName, [{outdir,Outdir1},
+ {path,P},silent]),
+ ?line Tar1 = filename:join(Outdir1,LatestName++".tar.gz"),
+ ?line true = filelib:is_file(Tar1),
+ ?line ok = file:delete(Tar1),
+
+ %% c) relative path
+ ?line {ok,_,[]} = systools:make_tar(LatestName, [{outdir,"./outdir1"},
+ {path,P},silent]),
+ ?line true = filelib:is_file(Tar1),
+ ?line ok = file:delete(Tar1),
+
+ %% d) absolute but incorrect path
+ ?line Tar2 = filename:join(Outdir2,LatestName++".tar.gz"),
+ ?line {error,_,{tar_error,{open,Tar2,{Tar2,enoent}}}} =
+ systools:make_tar(LatestName, [{outdir,Outdir2},{path,P},silent]),
+
+ %% e) relative but incorrect path
+ ?line {error,_,{tar_error,{open,_,_}}} =
+ systools:make_tar(LatestName, [{outdir,"./outdir2"},{path,P},silent]),
+
+ %% f) with .rel in another directory than cwd
+ ?line ok = file:set_cwd(Outdir1),
+ ?line {ok,_,[]} = systools:make_tar(filename:join(PrivDir, LatestName),
+ [{outdir,"."},{path,P},silent]),
+ ?line true = filelib:is_file(Tar1),
+ ?line ok = file:set_cwd(LatestDir),
+
+ %% ===== Now test systools:make_relup =====
+ %% a) badarg
+ ?line {'EXIT', {{badarg, [{outdir,outdir1}]}, _}} =
+ (catch systools:make_relup(LatestName,[LatestName1],[LatestName1],
+ [{outdir,outdir1},
+ {path,P},silent])),
+
+ %% b) absolute path
+ Relup = filename:join(Outdir1, "relup"),
+ ?line {ok,_,_,[]} = systools:make_relup(LatestName,[LatestName1],[LatestName1],
+ [{outdir,Outdir1},
+ {path,P},silent]),
+ ?line true = filelib:is_file(Relup),
+ ?line ok = file:delete(Relup),
+
+ %% c) relative path
+ ?line {ok,_,_,[]} = systools:make_relup(LatestName,[LatestName1],[LatestName1],
+ [{outdir,"./outdir1"},
+ {path,P},silent]),
+ ?line true = filelib:is_file(Relup),
+ ?line ok = file:delete(Relup),
+
+ %% d) absolute but incorrect path
+ ?line {error,_,{file_problem,{"relup",enoent}}} =
+ systools:make_relup(LatestName,[LatestName1],[LatestName1],
+ [{outdir,Outdir2},{path,P},silent]),
+
+ %% e) relative but incorrect path
+ ?line {error,_,{file_problem,{"relup",enoent}}} =
+ systools:make_relup(LatestName,[LatestName1],[LatestName1],
+ [{outdir,"./outdir2"},{path,P},silent]),
+
+ %% f) with .rel in another directory than cwd
+ %% -- not necessary to test since relup by default is placed in
+ %% cwd, not in the same directory as the .rel file --
+
+ %% Change back to previous working directory
+ ?line ok = file:set_cwd(OldDir),
+ ok.
+
+
+%%%%%%
+%%%%%% Utilities
+%%%%%%
+
+check_script_path(RelName) ->
+ {ok, [Conts]} = read_script_file(RelName),
+ {script, {_, _}, ListOfThings} = Conts,
+ case lists:keysearch(path, 1, ListOfThings) of
+ {value, {path, [$$,$R,$O,$O,$T | _]}} -> %"$ROOT..."
+ false;
+ _ -> ok
+ end.
+
+check_var_script_file(VarDirs, NoExistDirs, RelName) ->
+ {ok, [Conts]} = read_script_file(RelName),
+ {script, {_, _}, ListOfThings} = Conts,
+ AllPaths = lists:append(lists:map(fun({path, P}) -> P;
+ (_) -> []
+ end,
+ ListOfThings)),
+ case lists:filter(fun(VarDir) -> lists:member(VarDir, AllPaths) end,
+ VarDirs) of
+ VarDirs ->
+ ok;
+ _ ->
+ test_server:fail("All variable dirs not in generated script")
+ end,
+ case lists:filter(fun(NoExistDir) -> lists:member(NoExistDir, AllPaths) end,
+ NoExistDirs) of
+ [] ->
+ ok;
+ _ ->
+ test_server:fail("Unexpected dirs in generated script")
+ end.
+
+check_include_script(RelName, ExpectedLoad, ExpectedStart) ->
+ {ok, [Conts]} = read_script_file(RelName),
+ {script, {_, _}, ListOfThings} = Conts,
+
+ %% Check that the applications are loaded in given order !
+ ActualLoad =
+ [App || {apply,{application,load,[{application,App,_}]}} <- ListOfThings,
+ App=/=kernel,
+ App=/=stdlib],
+
+ if ActualLoad =:= ExpectedLoad -> ok;
+ true -> test_server:fail({bad_load_order, ActualLoad, ExpectedLoad})
+ end,
+
+ %% Check that applications are started in given order !
+ ActualStart =
+ [App || {apply,{application,start_boot,[App|_]}} <- ListOfThings,
+ App =/= kernel,
+ App =/= stdlib],
+
+ if ActualStart =:= ExpectedStart -> ok;
+ true -> test_server:fail({bad_start_order, ActualStart,ExpectedStart})
+ end,
+
+ ok.
+
+read_script_file(RelName) ->
+ file:consult(RelName ++ ".script").
+
+check_var_tar(Variable, RelName) ->
+ Expected = tar_name(Variable),
+ case check_tar(Expected,RelName) of
+ ok ->
+ ok;
+ {error, {erroneous_tar_file, _, missing, _}} ->
+ {error, {not_generated, Expected}}
+ end.
+
+exists_tar_file(Name) ->
+ File = tar_name(Name),
+ case filelib:is_regular(File) of
+ true ->
+ ok = file:delete(File),
+ true;
+ _ ->
+ false
+ end.
+
+%% Take a snap of the generated tar file and check if a certain
+%% file is included.
+%% This ensures at least that the tar file is generated.
+check_tar(File, RelName) ->
+ TarContents = tar_contents(RelName),
+ case lists:member(File,TarContents) of
+ true -> ok;
+ _ -> {error, {erroneous_tar_file, tar_name(RelName), missing, File}}
+ end.
+
+%% Check that the given files exist in the tar file, and that they are
+%% not symlinks
+check_tar_regular(PrivDir, Files, RelName) ->
+ TmpDir = fname(PrivDir,tmp),
+ ok = file:make_dir(TmpDir),
+ ok = erl_tar:extract(tar_name(RelName),
+ [{files,Files},{cwd,TmpDir},compressed]),
+ R = lists:foldl(fun(File,Acc) ->
+ case file:read_link_info(fname(TmpDir,File)) of
+ {ok,#file_info{type=regular}} ->
+ Acc;
+ {ok,#file_info{type=Other}} ->
+ [{File,Other}|Acc];
+ _ ->
+ [{File,missing}|Acc]
+ end
+ end,
+ [],
+ Files),
+ delete_tree(TmpDir),
+ case R of
+ [] ->
+ ok;
+ NotThere ->
+ {error,{erroneous_tar_file,tar_name(RelName),NotThere}}
+ end.
+
+delete_tree(Dir) ->
+ case filelib:is_dir(Dir) of
+ true ->
+ {ok,Files} = file:list_dir(Dir),
+ lists:foreach(fun(File) -> delete_tree(filename:join(Dir,File)) end,
+ Files),
+ file:del_dir(Dir);
+ false ->
+ ok = file:delete(Dir)
+ end.
+
+tar_contents(Name) ->
+ {ok, Cont} = erl_tar:table(Name ++ ".tar.gz", [compressed]),
+ Cont.
+
+tar_name(Name) ->
+ Name ++ ".tar.gz".
+
+create_script(latest,Config) ->
+ ?line PrivDir = ?privdir,
+ ?line Name = fname(PrivDir, latest),
+ ?line Apps = application_controller:which_applications(),
+ ?line {value,{_,_,KernelVer}} = lists:keysearch(kernel,1,Apps),
+ ?line {value,{_,_,StdlibVer}} = lists:keysearch(stdlib,1,Apps),
+ ?line {ok,Fd} = file:open(Name++".rel",write),
+ ?line io:format(Fd,
+ "{release, {\"Test release 3\", \"LATEST\"}, \n"
+ " {erts, \"4.4\"}, \n"
+ " [{kernel, \"~s\"}, {stdlib, \"~s\"}, \n"
+ " {db, \"2.1\"}, {fe, \"3.1\"}]}.\n",
+ [KernelVer,StdlibVer]),
+ ?line ok = file:close(Fd),
+ {filename:dirname(Name), filename:basename(Name)};
+create_script(latest_no_mod_vsn,Config) ->
+ ?line PrivDir = ?privdir,
+ ?line Name = fname(PrivDir, latest),
+ ?line Apps = application_controller:which_applications(),
+ ?line {value,{_,_,KernelVer}} = lists:keysearch(kernel,1,Apps),
+ ?line {value,{_,_,StdlibVer}} = lists:keysearch(stdlib,1,Apps),
+ ?line {ok,Fd} = file:open(Name++".rel",write),
+ ?line io:format(Fd,
+ "{release, {\"Test release 3\", \"LATESTNOMOD\"}, \n"
+ " {erts, \"4.4\"}, \n"
+ " [{kernel, \"~s\"}, {stdlib, \"~s\"}, \n"
+ " {db, \"3.1\"}, {fe, \"3.1\"}]}.\n",
+ [KernelVer,StdlibVer]),
+ ?line ok = file:close(Fd),
+ {filename:dirname(Name), filename:basename(Name)};
+create_script(latest0,Config) ->
+ ?line PrivDir = ?privdir,
+ ?line Name = fname(PrivDir, 'latest-1'),
+ ?line {ok,Fd} = file:open(Name++".rel",write),
+ ?line io:format(Fd,
+ "{release, {\"Test release 2\", \"LATEST0\"}, \n"
+ " {erts, \"4.4\"}, \n"
+ " [{kernel, \"1.0\"}, {stdlib, \"1.0\"}, \n"
+ " {db, \"2.1\"}, {fe, \"3.1\"}]}.\n",
+ []),
+ ?line ok = file:close(Fd),
+ {filename:dirname(Name), filename:basename(Name)};
+create_script(latest1,Config) ->
+ ?line PrivDir = ?privdir,
+ ?line Name = fname(PrivDir, latest),
+ ?line {ok,Fd} = file:open(Name++".rel",write),
+ ?line io:format(Fd,
+ "{release, {\"Test release 2\", \"LATEST1\"}, \n"
+ " {erts, \"4.4\"}, \n"
+ " [{kernel, \"1.0\"}, {stdlib, \"1.0\"}, \n"
+ " {db, \"1.0\"}, {fe, \"3.1\"}]}.\n",
+ []),
+ ?line ok = file:close(Fd),
+ {filename:dirname(Name), filename:basename(Name)};
+create_script(latest2,Config) ->
+ ?line PrivDir = ?privdir,
+ ?line Name = fname(PrivDir, 'latest-2'),
+ ?line {ok,Fd} = file:open(Name++".rel",write),
+ ?line io:format(Fd,
+ "{release, {\"Test release 1\", \"LATEST2\"}, \n"
+ " {erts, \"4.3\"}, \n"
+ " [{kernel, \"1.0\"}, {stdlib, \"1.0\"}, \n"
+ " {db, \"1.0\"}, {fe, \"2.1\"}]}.\n",
+ []),
+ ?line ok = file:close(Fd),
+ {filename:dirname(Name), filename:basename(Name)};
+create_script(latest_small,Config) ->
+ ?line PrivDir = ?privdir,
+ ?line Name = fname(PrivDir, 'latest-small'),
+ ?line {ok,Fd} = file:open(Name++".rel",write),
+ ?line io:format(Fd,
+ "{release, {\"Test release 2\", \"LATEST_SMALL\"}, \n"
+ " {erts, \"4.4\"}, \n"
+ " [{kernel, \"1.0\"}, {stdlib, \"1.0\"}, \n"
+ " {fe, \"3.1\"}]}.\n",
+ []),
+ ?line ok = file:close(Fd),
+ {filename:dirname(Name), filename:basename(Name)};
+create_script(latest_small0,Config) -> %Differs in fe vsn
+ ?line PrivDir = ?privdir,
+ ?line Name = fname(PrivDir, 'latest-small0'),
+ ?line {ok,Fd} = file:open(Name++".rel",write),
+ ?line io:format(Fd,
+ "{release, {\"Test release 2\", \"LATEST_SMALL0\"}, \n"
+ " {erts, \"4.4\"}, \n"
+ " [{kernel, \"1.0\"}, {stdlib, \"1.0\"}, \n"
+ " {fe, \"2.1\"}]}.\n",
+ []),
+ ?line ok = file:close(Fd),
+ {filename:dirname(Name), filename:basename(Name)};
+create_script(latest_small1,Config) ->
+ ?line PrivDir = ?privdir,
+ ?line Name = fname(PrivDir, 'latest-small1'),
+ ?line {ok,Fd} = file:open(Name++".rel",write),
+ ?line io:format(Fd,
+ "{release, {\"Test release 2\", \"LATEST_SMALL1\"}, \n"
+ " {erts, \"4.4\"}, \n"
+ " [{kernel, \"1.0\"}, {stdlib, \"1.0\"}, \n"
+ " {fe, \"500.18.7\"}]}.\n",
+ []),
+ ?line ok = file:close(Fd),
+ {filename:dirname(Name), filename:basename(Name)};
+create_script(latest_nokernel,Config) ->
+ ?line PrivDir = ?privdir,
+ ?line Name = fname(PrivDir, 'latest-nokernel'),
+ ?line {ok,Fd} = file:open(Name++".rel",write),
+ ?line io:format(Fd,
+ "{release, {\"Test release 3\", \"LATEST_NOKERNEL\"}, \n"
+ " {erts, \"4.4\"}, \n"
+ " [{db, \"2.1\"}, {fe, \"3.1\"}]}.\n",
+ []),
+ ?line ok = file:close(Fd),
+ {filename:dirname(Name), filename:basename(Name)};
+create_script(latest_app_start_type1,Config) ->
+ ?line PrivDir = ?privdir,
+ ?line Name = fname(PrivDir, latest_app_start_type1),
+ ?line ErtsVer = erlang:system_info(version),
+ ?line Apps = application_controller:which_applications(),
+ ?line {value,{_,_,KernelVer}} = lists:keysearch(kernel,1,Apps),
+ ?line {value,{_,_,StdlibVer}} = lists:keysearch(stdlib,1,Apps),
+ ?line {ok,Fd} = file:open(Name++".rel",write),
+ ?line RelfileContent =
+ {release,{"Test release", "1"},
+ {erts,ErtsVer},
+ [{kernel,KernelVer},
+ {stdlib,StdlibVer}]},
+ ?line io:format(Fd,"~p.~n",[RelfileContent]),
+ ?line ok = file:close(Fd),
+ {filename:dirname(Name), filename:basename(Name)};
+create_script(latest_app_start_type2,Config) ->
+ ?line PrivDir = ?privdir,
+ ?line Name = fname(PrivDir, latest_app_start_type2),
+ ?line ErtsVer = erlang:system_info(version),
+ ?line Apps = application_controller:which_applications(),
+ ?line {value,{_,_,KernelVer}} = lists:keysearch(kernel,1,Apps),
+ ?line {value,{_,_,StdlibVer}} = lists:keysearch(stdlib,1,Apps),
+ ?line OtherApps = [{mnesia,permanent},
+ {sasl,transient},
+ {webtool,temporary},
+ {snmp,load},
+ {xmerl,none}],
+ ?line lists:foreach(fun({App,_}) -> application:load(App) end,
+ OtherApps),
+ ?line Loaded = application:loaded_applications(),
+ ?line OtherAppsRel =
+ lists:map(fun({App,StartType}) ->
+ {_,_,Ver} = lists:keyfind(App,1,Loaded),
+ {App,Ver,StartType}
+ end,
+ OtherApps),
+ ?line {ok,Fd} = file:open(Name++".rel",write),
+ ?line RelfileContent =
+ {release,{"Test release", "2"},
+ {erts,ErtsVer},
+ [{kernel,KernelVer},
+ {stdlib,StdlibVer} | OtherAppsRel]},
+ ?line io:format(Fd,"~p.~n",[RelfileContent]),
+ ?line ok = file:close(Fd),
+ {filename:dirname(Name), filename:basename(Name)}.
+
+create_include_files(inc1, Config) ->
+ ?line PrivDir = ?privdir,
+ ?line Name = fname(PrivDir, inc1),
+ create_apps(PrivDir),
+
+ ?line Apps = application_controller:which_applications(),
+ ?line {value,{_,_,KernelVer}} = lists:keysearch(kernel,1,Apps),
+ ?line {value,{_,_,StdlibVer}} = lists:keysearch(stdlib,1,Apps),
+
+ Rel = "{release, {\"test\",\"R1A\"}, {erts, \"45\"},\n"
+ " [{kernel, \"" ++ KernelVer ++ "\"}, {stdlib, \""
+ ++ StdlibVer ++ "\"},\n"
+ " {t6, \"1.0\"}, {t5, \"1.0\"}, \n"
+ " {t4, \"1.0\"}, {t3, \"1.0\"}, {t2, \"1.0\"}, \n"
+ " {t1, \"1.0\"}]}.\n",
+ file:write_file(Name ++ ".rel", list_to_binary(Rel)),
+ {filename:dirname(Name), filename:basename(Name)};
+
+create_include_files(inc2, Config) ->
+ ?line PrivDir = ?privdir,
+ ?line Name = fname(PrivDir, inc2),
+ create_apps(PrivDir),
+
+ ?line Apps = application_controller:which_applications(),
+ ?line {value,{_,_,KernelVer}} = lists:keysearch(kernel,1,Apps),
+ ?line {value,{_,_,StdlibVer}} = lists:keysearch(stdlib,1,Apps),
+
+ %% t6 does not include t5 !
+ Rel = "{release, {\"test\",\"R1A\"}, {erts, \"45\"},\n"
+ " [{kernel, \"" ++ KernelVer ++ "\"}, {stdlib, \""
+ ++ StdlibVer ++ "\"},\n"
+ " {t6, \"1.0\", [t4]}, {t5, \"1.0\"}, \n"
+ " {t4, \"1.0\"}, {t3, \"1.0\"}, {t2, \"1.0\"}, \n"
+ " {t1, \"1.0\"}]}.\n",
+ file:write_file(Name ++ ".rel", list_to_binary(Rel)),
+ {filename:dirname(Name), filename:basename(Name)};
+
+create_include_files(inc3, Config) ->
+ ?line PrivDir = ?privdir,
+ ?line Name = fname(PrivDir, inc3),
+ create_apps(PrivDir),
+
+ ?line Apps = application_controller:which_applications(),
+ ?line {value,{_,_,KernelVer}} = lists:keysearch(kernel,1,Apps),
+ ?line {value,{_,_,StdlibVer}} = lists:keysearch(stdlib,1,Apps),
+
+ %% t3 does not include t2 !
+ Rel = "{release, {\"test\",\"R1A\"}, {erts, \"45\"},\n"
+ " [{kernel, \"" ++ KernelVer ++ "\"}, {stdlib, \""
+ ++ StdlibVer ++ "\"},\n"
+ " {t6, \"1.0\"}, {t5, \"1.0\"}, \n"
+ " {t4, \"1.0\"}, {t3, \"1.0\", []}, {t2, \"1.0\"}, \n"
+ " {t1, \"1.0\"}]}.\n",
+ file:write_file(Name ++ ".rel", list_to_binary(Rel)),
+ {filename:dirname(Name), filename:basename(Name)};
+
+create_include_files(inc4, Config) ->
+ ?line PrivDir = ?privdir,
+ ?line Name = fname(PrivDir, inc4),
+ create_apps(PrivDir),
+
+ ?line Apps = application_controller:which_applications(),
+ ?line {value,{_,_,KernelVer}} = lists:keysearch(kernel,1,Apps),
+ ?line {value,{_,_,StdlibVer}} = lists:keysearch(stdlib,1,Apps),
+
+ %% t3 does not include t2 !
+ %% t6 does not include t5 !
+ Rel = "{release, {\"test\",\"R1A\"}, {erts, \"45\"},\n"
+ " [{kernel, \"" ++ KernelVer ++ "\"}, {stdlib, \""
+ ++ StdlibVer ++ "\"},\n"
+ " {t6, \"1.0\", [t4]}, {t5, \"1.0\"}, \n"
+ " {t4, \"1.0\"}, {t3, \"1.0\", []}, {t2, \"1.0\"}, \n"
+ " {t1, \"1.0\"}]}.\n",
+ file:write_file(Name ++ ".rel", list_to_binary(Rel)),
+ {filename:dirname(Name), filename:basename(Name)};
+
+create_include_files(inc5, Config) ->
+ ?line PrivDir = ?privdir,
+ ?line Name = fname(PrivDir, inc5),
+ create_apps(PrivDir),
+
+ ?line Apps = application_controller:which_applications(),
+ ?line {value,{_,_,KernelVer}} = lists:keysearch(kernel,1,Apps),
+ ?line {value,{_,_,StdlibVer}} = lists:keysearch(stdlib,1,Apps),
+
+ %% t6 does not include t5 !
+ %% exclude t5.
+ Rel = "{release, {\"test\",\"R1A\"}, {erts, \"45\"},\n"
+ " [{kernel, \"" ++ KernelVer ++ "\"}, {stdlib, \""
+ ++ StdlibVer ++ "\"},\n"
+ " {t6, \"1.0\", [t4]}, \n"
+ " {t4, \"1.0\"}, {t3, \"1.0\", []}, {t2, \"1.0\"}, \n"
+ " {t1, \"1.0\"}]}.\n",
+ file:write_file(Name ++ ".rel", list_to_binary(Rel)),
+ {filename:dirname(Name), filename:basename(Name)};
+
+create_include_files(inc6, Config) ->
+ ?line PrivDir = ?privdir,
+ ?line Name = fname(PrivDir, inc6),
+ create_apps(PrivDir),
+
+ ?line Apps = application_controller:which_applications(),
+ ?line {value,{_,_,KernelVer}} = lists:keysearch(kernel,1,Apps),
+ ?line {value,{_,_,StdlibVer}} = lists:keysearch(stdlib,1,Apps),
+
+ %% t3 does include non existing t2 !
+ Rel = "{release, {\"test\",\"R1A\"}, {erts, \"45\"},\n"
+ " [{kernel, \"" ++ KernelVer ++ "\"}, {stdlib, \""
+ ++ StdlibVer ++ "\"},\n"
+ " {t6, \"1.0\"}, {t5, \"1.0\"}, \n"
+ " {t4, \"1.0\"}, {t3, \"1.0\"}, \n"
+ " {t1, \"1.0\"}]}.\n",
+ file:write_file(Name ++ ".rel", list_to_binary(Rel)),
+ {filename:dirname(Name), filename:basename(Name)};
+
+create_include_files(inc7, Config) ->
+ ?line PrivDir = ?privdir,
+ ?line Name = fname(PrivDir, inc7),
+ create_apps(PrivDir),
+ create_app(t7, PrivDir),
+
+ ?line Apps = application_controller:which_applications(),
+ ?line {value,{_,_,KernelVer}} = lists:keysearch(kernel,1,Apps),
+ ?line {value,{_,_,StdlibVer}} = lists:keysearch(stdlib,1,Apps),
+
+ %% t7 and t6 does include t5 !
+ Rel = "{release, {\"test\",\"R1A\"}, {erts, \"45\"},\n"
+ " [{kernel, \"" ++ KernelVer ++ "\"}, {stdlib, \""
+ ++ StdlibVer ++ "\"},\n"
+ " {t7, \"1.0\"}, {t6, \"1.0\"}, {t5, \"1.0\"}, \n"
+ " {t4, \"1.0\"}, {t3, \"1.0\"}, {t2, \"1.0\"}, \n"
+ " {t1, \"1.0\"}]}.\n",
+ file:write_file(Name ++ ".rel", list_to_binary(Rel)),
+ {filename:dirname(Name), filename:basename(Name)};
+
+create_include_files(inc8, Config) ->
+ ?line PrivDir = ?privdir,
+ ?line Name = fname(PrivDir, inc8),
+ create_circular_apps(PrivDir),
+
+ ?line Apps = application_controller:which_applications(),
+ ?line {value,{_,_,KernelVer}} = lists:keysearch(kernel,1,Apps),
+ ?line {value,{_,_,StdlibVer}} = lists:keysearch(stdlib,1,Apps),
+
+ %% t8 uses t9 and t10 includes t9 !
+ Rel = "{release, {\"test\",\"R1A\"}, {erts, \"45\"},\n"
+ " [{kernel, \"" ++ KernelVer ++ "\"}, {stdlib, \""
+ ++ StdlibVer ++ "\"},\n"
+ " {t8, \"1.0\"}, {t9, \"1.0\"}, {t10, \"1.0\"}]}.\n",
+ file:write_file(Name ++ ".rel", list_to_binary(Rel)),
+ {filename:dirname(Name), filename:basename(Name)};
+
+create_include_files(inc9, Config) ->
+ ?line PrivDir = ?privdir,
+ ?line Name = fname(PrivDir, inc9),
+ create_circular_apps(PrivDir),
+
+ ?line Apps = application_controller:which_applications(),
+ ?line {value,{_,_,KernelVer}} = lists:keysearch(kernel,1,Apps),
+ ?line {value,{_,_,StdlibVer}} = lists:keysearch(stdlib,1,Apps),
+
+ %% t8 uses t9, t9 uses t10 and t10 includes t8 ==> circular !!
+ Rel = "{release, {\"test\",\"R1A\"}, {erts, \"45\"},\n"
+ " [{kernel, \"" ++ KernelVer ++ "\"}, {stdlib, \""
+ ++ StdlibVer ++ "\"},\n"
+ " {t8, \"1.0\"}, {t9, \"1.0\"}, {t10, \"1.0\", [t8]}]}.\n",
+ file:write_file(Name ++ ".rel", list_to_binary(Rel)),
+ {filename:dirname(Name), filename:basename(Name)};
+
+create_include_files(inc10, Config) ->
+ ?line PrivDir = ?privdir,
+ ?line Name = fname(PrivDir, inc10),
+ create_circular_apps(PrivDir),
+
+ ?line Apps = application_controller:which_applications(),
+ ?line {value,{_,_,KernelVer}} = lists:keysearch(kernel,1,Apps),
+ ?line {value,{_,_,StdlibVer}} = lists:keysearch(stdlib,1,Apps),
+
+ %% t9 tries to include not specified (in .app file) application !
+ Rel = "{release, {\"test\",\"R1A\"}, {erts, \"45\"},\n"
+ " [{kernel, \"" ++ KernelVer ++ "\"}, {stdlib, \""
+ ++ StdlibVer ++ "\"},\n"
+ " {t8, \"1.0\"}, {t9, \"1.0\", [t7]}, {t10, \"1.0\"}]}.\n",
+ file:write_file(Name ++ ".rel", list_to_binary(Rel)),
+ {filename:dirname(Name), filename:basename(Name)};
+
+create_include_files(inc11, Config) ->
+ ?line PrivDir = ?privdir,
+ ?line Name = fname(PrivDir, inc11),
+ create_apps2(PrivDir),
+
+ ?line Apps = application_controller:which_applications(),
+ ?line {value,{_,_,KernelVer}} = lists:keysearch(kernel,1,Apps),
+ ?line {value,{_,_,StdlibVer}} = lists:keysearch(stdlib,1,Apps),
+
+ Rel = "{release, {\"test\",\"R1A\"}, {erts, \"45\"},\n"
+ " [{kernel, \"" ++ KernelVer ++ "\"}, {stdlib, \""
+ ++ StdlibVer ++ "\"},\n"
+ " {t11, \"1.0\"}, \n"
+ " {t12, \"1.0\"}, \n"
+ " {t13, \"1.0\"}]}.\n",
+ file:write_file(Name ++ ".rel", list_to_binary(Rel)),
+ {filename:dirname(Name), filename:basename(Name)};
+
+create_include_files(otp_3065, Config) ->
+ ?line PrivDir = ?privdir,
+ ?line Name = fname(PrivDir, otp_3065),
+ create_apps_3065(PrivDir),
+
+ ?line Apps = application_controller:which_applications(),
+ ?line {value,{_,_,KernelVer}} = lists:keysearch(kernel,1,Apps),
+ ?line {value,{_,_,StdlibVer}} = lists:keysearch(stdlib,1,Apps),
+
+ Rel = "{release, {\"test\",\"R1A\"}, {erts, \"45\"},\n"
+ " [{kernel, \"" ++ KernelVer ++ "\"}, {stdlib, \""
+ ++ StdlibVer ++ "\"},\n"
+ " {chAts, \"1.0\"}, {aa12, \"1.0\"}, \n"
+ " {chTraffic, \"1.0\"}]}.\n",
+ file:write_file(Name ++ ".rel", list_to_binary(Rel)),
+ {filename:dirname(Name), filename:basename(Name)}.
+
+create_apps(Dir) ->
+ T1 = "{application, t1,\n"
+ " [{vsn, \"1.0\"},\n"
+ " {description, \"test\"},\n"
+ " {modules, []},\n"
+ " {applications, [kernel, stdlib]},\n"
+ " {registered, []}]}.\n",
+ file:write_file(fname(Dir, 't1.app'), list_to_binary(T1)),
+
+ T2 = "{application, t2,\n"
+ " [{vsn, \"1.0\"},\n"
+ " {description, \"test\"},\n"
+ " {modules, []},\n"
+ " {applications, [t1]},\n"
+ " {registered, []}]}.\n",
+ file:write_file(fname(Dir, 't2.app'), list_to_binary(T2)),
+
+ T3 = "{application, t3,\n"
+ " [{vsn, \"1.0\"},\n"
+ " {description, \"test\"},\n"
+ " {modules, []},\n"
+ " {applications, []},\n"
+ " {included_applications, [t2]},\n"
+ " {registered, []}]}.\n",
+ file:write_file(fname(Dir, 't3.app'), list_to_binary(T3)),
+
+ T4 = "{application, t4,\n"
+ " [{vsn, \"1.0\"},\n"
+ " {description, \"test\"},\n"
+ " {modules, []},\n"
+ " {applications, [t3]},\n"
+ " {included_applications, []},\n"
+ " {registered, []}]}.\n",
+ file:write_file(fname(Dir, 't4.app'), list_to_binary(T4)),
+
+ T5 = "{application, t5,\n"
+ " [{vsn, \"1.0\"},\n"
+ " {description, \"test\"},\n"
+ " {modules, []},\n"
+ " {applications, [t3]},\n"
+ " {included_applications, []},\n"
+ " {registered, []}]}.\n",
+ file:write_file(fname(Dir, 't5.app'), list_to_binary(T5)),
+
+ T6 = "{application, t6,\n"
+ " [{vsn, \"1.0\"},\n"
+ " {description, \"test\"},\n"
+ " {modules, []},\n"
+ " {applications, []},\n"
+ " {included_applications, [t4, t5]},\n"
+ " {registered, []}]}.\n",
+ file:write_file(fname(Dir, 't6.app'), list_to_binary(T6)).
+
+create_app(t7, Dir) ->
+ T7 = "{application, t7,\n"
+ " [{vsn, \"1.0\"},\n"
+ " {description, \"test\"},\n"
+ " {modules, []},\n"
+ " {applications, []},\n"
+ " {included_applications, [t5]},\n"
+ " {registered, []}]}.\n",
+ file:write_file(fname(Dir, 't7.app'), list_to_binary(T7)).
+
+create_circular_apps(Dir) ->
+ T8 = "{application, t8,\n"
+ " [{vsn, \"1.0\"},\n"
+ " {description, \"test\"},\n"
+ " {modules, []},\n"
+ " {applications, [t9]},\n"
+ " {included_applications, []},\n"
+ " {registered, []}]}.\n",
+ file:write_file(fname(Dir, 't8.app'), list_to_binary(T8)),
+
+ T9 = "{application, t9,\n"
+ " [{vsn, \"1.0\"},\n"
+ " {description, \"test\"},\n"
+ " {modules, []},\n"
+ " {applications, [t10]},\n"
+ " {included_applications, []},\n"
+ " {registered, []}]}.\n",
+ file:write_file(fname(Dir, 't9.app'), list_to_binary(T9)),
+
+ T10 = "{application, t10,\n"
+ " [{vsn, \"1.0\"},\n"
+ " {description, \"test\"},\n"
+ " {modules, []},\n"
+ " {applications, []},\n"
+ " {included_applications, [t8, t9]},\n"
+ " {registered, []}]}.\n",
+ file:write_file(fname(Dir, 't10.app'), list_to_binary(T10)).
+
+create_apps2(Dir) ->
+ T11 = "{application, t11,\n"
+ " [{vsn, \"1.0\"},\n"
+ " {description, \"test\"},\n"
+ " {modules, []},\n"
+ " {applications, []},\n"
+ " {included_applications, [t13]},\n"
+ " {registered, []}]}.\n",
+ file:write_file(fname(Dir, 't11.app'), list_to_binary(T11)),
+
+ T12 = "{application, t12,\n"
+ " [{vsn, \"1.0\"},\n"
+ " {description, \"test\"},\n"
+ " {modules, []},\n"
+ " {applications, [t11]},\n"
+ " {registered, []}]}.\n",
+ file:write_file(fname(Dir, 't12.app'), list_to_binary(T12)),
+
+ T13 = "{application, t13,\n"
+ " [{vsn, \"1.0\"},\n"
+ " {description, \"test\"},\n"
+ " {modules, []},\n"
+ " {applications, []},\n"
+ " {included_applications, []},\n"
+ " {registered, []}]}.\n",
+ file:write_file(fname(Dir, 't13.app'), list_to_binary(T13)).
+
+
+
+create_apps_3065(Dir) ->
+ T11 = "{application, chTraffic,\n"
+ " [{vsn, \"1.0\"},\n"
+ " {description, \"test\"},\n"
+ " {modules, []},\n"
+ " {applications, []},\n"
+ " {included_applications, [chAts]},\n"
+ " {registered, []}]}.\n",
+ file:write_file(fname(Dir, 'chTraffic.app'), list_to_binary(T11)),
+
+ T12 = "{application, chAts,\n"
+ " [{vsn, \"1.0\"},\n"
+ " {description, \"test\"},\n"
+ " {modules, []},\n"
+ " {applications, []},\n"
+ " {included_applications, [aa12]},\n"
+ " {registered, []}]}.\n",
+ file:write_file(fname(Dir, 'chAts.app'), list_to_binary(T12)),
+
+ T13 = "{application, aa12,\n"
+ " [{vsn, \"1.0\"},\n"
+ " {description, \"test\"},\n"
+ " {modules, []},\n"
+ " {applications, [chAts]},\n"
+ " {included_applications, []},\n"
+ " {registered, []}]}.\n",
+ file:write_file(fname(Dir, 'aa12.app'), list_to_binary(T13)).
+
+fname(N) ->
+ filename:join(N).
+
+fname(Dir, Basename) ->
+ filename:join(Dir, Basename).
diff --git a/lib/sasl/test/systools_SUITE_data/d_bad_app_vsn/lib/db-2.1/ebin/db.app b/lib/sasl/test/systools_SUITE_data/d_bad_app_vsn/lib/db-2.1/ebin/db.app
new file mode 100644
index 0000000000..d375768b99
--- /dev/null
+++ b/lib/sasl/test/systools_SUITE_data/d_bad_app_vsn/lib/db-2.1/ebin/db.app
@@ -0,0 +1,8 @@
+{application, db,
+ [{description, "ERICSSON NR FOR DB"},
+ {vsn, "2.0"},
+ {modules, [{db1, "1.0"}, {db2, "1.0"}]},
+ {registered, []},
+ {applications, []},
+ {env, []},
+ {start, {db1, start, []}}]}.
diff --git a/lib/sasl/test/systools_SUITE_data/d_bad_app_vsn/lib/db-2.1/ebin/db.appup b/lib/sasl/test/systools_SUITE_data/d_bad_app_vsn/lib/db-2.1/ebin/db.appup
new file mode 100644
index 0000000000..621838f0b4
--- /dev/null
+++ b/lib/sasl/test/systools_SUITE_data/d_bad_app_vsn/lib/db-2.1/ebin/db.appup
@@ -0,0 +1,20 @@
+%%
+%% Release upgrade script for db (data base)
+%%
+
+{
+ "2.1",
+%%% Upgrade from:
+ [
+ {"1.0", [{update, db1, soft, soft_purge, soft_purge, []},
+ {update, db2, soft, soft_purge, soft_purge, [db1]}]},
+ {"1.1", [{update, db1, soft, soft_purge, soft_purge, []},
+ {update, db2, soft, soft_purge, soft_purge, [db1]}]}
+ ],
+
+%%% Downgrade to:
+ [
+ {"1.0", [{update, db1, soft, soft_purge, soft_purge, []},
+ {update, db2, soft, soft_purge, soft_purge, [db1]}]}
+ ]
+}.
diff --git a/lib/sasl/test/systools_SUITE_data/d_bad_app_vsn/lib/db-2.1/src/db1.erl b/lib/sasl/test/systools_SUITE_data/d_bad_app_vsn/lib/db-2.1/src/db1.erl
new file mode 100644
index 0000000000..a17640316e
--- /dev/null
+++ b/lib/sasl/test/systools_SUITE_data/d_bad_app_vsn/lib/db-2.1/src/db1.erl
@@ -0,0 +1,2 @@
+-module(db2).
+-vsn("1.0").
diff --git a/lib/sasl/test/systools_SUITE_data/d_bad_app_vsn/lib/db-2.1/src/db2.erl b/lib/sasl/test/systools_SUITE_data/d_bad_app_vsn/lib/db-2.1/src/db2.erl
new file mode 100644
index 0000000000..a17640316e
--- /dev/null
+++ b/lib/sasl/test/systools_SUITE_data/d_bad_app_vsn/lib/db-2.1/src/db2.erl
@@ -0,0 +1,2 @@
+-module(db2).
+-vsn("1.0").
diff --git a/lib/sasl/test/systools_SUITE_data/d_bad_app_vsn/lib/fe-3.1/ebin/fe.app b/lib/sasl/test/systools_SUITE_data/d_bad_app_vsn/lib/fe-3.1/ebin/fe.app
new file mode 100644
index 0000000000..d3bd85cda6
--- /dev/null
+++ b/lib/sasl/test/systools_SUITE_data/d_bad_app_vsn/lib/fe-3.1/ebin/fe.app
@@ -0,0 +1,8 @@
+{application, fe,
+ [{description, "ERICSSON NR FOR FE"},
+ {vsn, "3.1"},
+ {modules, [{fe1, "1.0"}, {fe2, "1.0"}, {fe3, "2.0"}]},
+ {registered, []},
+ {applications, []},
+ {env, []},
+ {start, {fe2, start, []}}]}.
diff --git a/lib/sasl/test/systools_SUITE_data/d_bad_app_vsn/lib/fe-3.1/ebin/fe.appup b/lib/sasl/test/systools_SUITE_data/d_bad_app_vsn/lib/fe-3.1/ebin/fe.appup
new file mode 100644
index 0000000000..4ab13c1bae
--- /dev/null
+++ b/lib/sasl/test/systools_SUITE_data/d_bad_app_vsn/lib/fe-3.1/ebin/fe.appup
@@ -0,0 +1,27 @@
+%%
+%% Release upgrade script for fe (front end)
+%%
+
+{
+ "3.1",
+%%% Upgrade from:
+ [
+ {"2.1.1", [{update, fe1, soft, soft_purge, soft_purge, []},
+ {update, fe2, soft, soft_purge, soft_purge, [fe1]},
+ {update, fe3, {{advanced, extra}, soft_purge, soft_purge,
+ [fe1, fe2]}
+ ]},
+
+ {"2.1", [{update, fe1, soft, soft_purge, soft_purge, []},
+ {update, fe2, soft, soft_purge, soft_purge, [fe1]},
+ {update, fe3, {advanced, extra}, soft_purge, soft_purge, [fe1, fe2]}
+ ]}
+ ],
+
+%%% Downgrade to:
+ [
+ {"2.1", [{update, fe2, soft, soft_purge, soft_purge, []},
+ {update, fe3, {advanced, extra}, soft_purge, soft_purge, [fe2]}
+ ]}
+ ]
+}.
diff --git a/lib/sasl/test/systools_SUITE_data/d_bad_app_vsn/lib/fe-3.1/src/fe1.erl b/lib/sasl/test/systools_SUITE_data/d_bad_app_vsn/lib/fe-3.1/src/fe1.erl
new file mode 100644
index 0000000000..aa5bfa8098
--- /dev/null
+++ b/lib/sasl/test/systools_SUITE_data/d_bad_app_vsn/lib/fe-3.1/src/fe1.erl
@@ -0,0 +1,2 @@
+-module(fe1).
+-vsn("1.0").
diff --git a/lib/sasl/test/systools_SUITE_data/d_bad_app_vsn/lib/fe-3.1/src/fe2.erl b/lib/sasl/test/systools_SUITE_data/d_bad_app_vsn/lib/fe-3.1/src/fe2.erl
new file mode 100644
index 0000000000..869f3b93c8
--- /dev/null
+++ b/lib/sasl/test/systools_SUITE_data/d_bad_app_vsn/lib/fe-3.1/src/fe2.erl
@@ -0,0 +1,2 @@
+-module(fe2).
+-vsn("1.0").
diff --git a/lib/sasl/test/systools_SUITE_data/d_bad_app_vsn/lib/fe-3.1/src/fe3.erl b/lib/sasl/test/systools_SUITE_data/d_bad_app_vsn/lib/fe-3.1/src/fe3.erl
new file mode 100644
index 0000000000..6473342f52
--- /dev/null
+++ b/lib/sasl/test/systools_SUITE_data/d_bad_app_vsn/lib/fe-3.1/src/fe3.erl
@@ -0,0 +1,2 @@
+-module(fe3).
+-vsn("2.0").
diff --git a/lib/sasl/test/systools_SUITE_data/d_bad_appup/lib/fe-3.1/ebin/fe.app b/lib/sasl/test/systools_SUITE_data/d_bad_appup/lib/fe-3.1/ebin/fe.app
new file mode 100644
index 0000000000..0696e2494c
--- /dev/null
+++ b/lib/sasl/test/systools_SUITE_data/d_bad_appup/lib/fe-3.1/ebin/fe.app
@@ -0,0 +1,7 @@
+{application, fe,
+ [{description, "ERICSSON NR FOR FE"},
+ {vsn, "3.1"},
+ {modules, [{fe1, "1.0"}, {fe2, "1.0"}, {fe3, "2.0"}]},
+ {registered, []},
+ {applications, []},
+ {mod, {fe1, []}}]}.
diff --git a/lib/sasl/test/systools_SUITE_data/d_bad_appup/lib/fe-3.1/ebin/fe.appup b/lib/sasl/test/systools_SUITE_data/d_bad_appup/lib/fe-3.1/ebin/fe.appup
new file mode 100644
index 0000000000..dac4c00851
--- /dev/null
+++ b/lib/sasl/test/systools_SUITE_data/d_bad_appup/lib/fe-3.1/ebin/fe.appup
@@ -0,0 +1,27 @@
+%%
+%% Release upgrade script for fe (front end)
+%%
+
+{
+ "3.1",
+%%% Upgrade from:
+ [
+ {"2.1.1" [{update, fe1, soft, soft_purge, soft_purge, []},
+ {update, fe2, soft, soft_purge, soft_purge, [fe1]},
+ {update, fe3, {advanced, extra}, soft_purge, soft_purge,
+ [fe1, fe2]}
+ ]},
+
+ {"2.1", [{update, fe1, soft, soft_purge, soft_purge, []},
+ {update, fe2, soft, soft_purge, soft_purge, [fe1]},
+ {update, fe3, {advanced, extra}, soft_purge, soft_purge, [fe1, fe2]}
+ ]}
+ ],
+
+%%% Downgrade to:
+ [
+ {"2.1", [{update, fe2, soft, soft_purge, soft_purge, []},
+ {update, fe3, {advanced, extra}, soft_purge, soft_purge, [fe2]}
+ ]}
+ ]
+}.
diff --git a/lib/sasl/test/systools_SUITE_data/d_bad_mod+warn/lib/db-2.1/ebin/db.app b/lib/sasl/test/systools_SUITE_data/d_bad_mod+warn/lib/db-2.1/ebin/db.app
new file mode 100644
index 0000000000..191919f8d3
--- /dev/null
+++ b/lib/sasl/test/systools_SUITE_data/d_bad_mod+warn/lib/db-2.1/ebin/db.app
@@ -0,0 +1,8 @@
+{application, db,
+ [{description, "ERICSSON NR FOR DB"},
+ {vsn, "2.1"},
+ {modules, [{db1, "1.0"}, {db2, "1.0"}]},
+ {registered, []},
+ {applications, []},
+ {env, []},
+ {start, {db1, start, []}}]}.
diff --git a/lib/sasl/test/systools_SUITE_data/d_bad_mod+warn/lib/db-2.1/ebin/db.appup b/lib/sasl/test/systools_SUITE_data/d_bad_mod+warn/lib/db-2.1/ebin/db.appup
new file mode 100644
index 0000000000..621838f0b4
--- /dev/null
+++ b/lib/sasl/test/systools_SUITE_data/d_bad_mod+warn/lib/db-2.1/ebin/db.appup
@@ -0,0 +1,20 @@
+%%
+%% Release upgrade script for db (data base)
+%%
+
+{
+ "2.1",
+%%% Upgrade from:
+ [
+ {"1.0", [{update, db1, soft, soft_purge, soft_purge, []},
+ {update, db2, soft, soft_purge, soft_purge, [db1]}]},
+ {"1.1", [{update, db1, soft, soft_purge, soft_purge, []},
+ {update, db2, soft, soft_purge, soft_purge, [db1]}]}
+ ],
+
+%%% Downgrade to:
+ [
+ {"1.0", [{update, db1, soft, soft_purge, soft_purge, []},
+ {update, db2, soft, soft_purge, soft_purge, [db1]}]}
+ ]
+}.
diff --git a/lib/sasl/test/systools_SUITE_data/d_bad_mod+warn/lib/db-2.1/src/db1.erl b/lib/sasl/test/systools_SUITE_data/d_bad_mod+warn/lib/db-2.1/src/db1.erl
new file mode 100644
index 0000000000..a17640316e
--- /dev/null
+++ b/lib/sasl/test/systools_SUITE_data/d_bad_mod+warn/lib/db-2.1/src/db1.erl
@@ -0,0 +1,2 @@
+-module(db2).
+-vsn("1.0").
diff --git a/lib/sasl/test/systools_SUITE_data/d_bad_mod+warn/lib/db-2.1/src/db2.erl b/lib/sasl/test/systools_SUITE_data/d_bad_mod+warn/lib/db-2.1/src/db2.erl
new file mode 100644
index 0000000000..a17640316e
--- /dev/null
+++ b/lib/sasl/test/systools_SUITE_data/d_bad_mod+warn/lib/db-2.1/src/db2.erl
@@ -0,0 +1,2 @@
+-module(db2).
+-vsn("1.0").
diff --git a/lib/sasl/test/systools_SUITE_data/d_bad_mod+warn/lib/fe-3.1/ebin/fe.app b/lib/sasl/test/systools_SUITE_data/d_bad_mod+warn/lib/fe-3.1/ebin/fe.app
new file mode 100644
index 0000000000..d3bd85cda6
--- /dev/null
+++ b/lib/sasl/test/systools_SUITE_data/d_bad_mod+warn/lib/fe-3.1/ebin/fe.app
@@ -0,0 +1,8 @@
+{application, fe,
+ [{description, "ERICSSON NR FOR FE"},
+ {vsn, "3.1"},
+ {modules, [{fe1, "1.0"}, {fe2, "1.0"}, {fe3, "2.0"}]},
+ {registered, []},
+ {applications, []},
+ {env, []},
+ {start, {fe2, start, []}}]}.
diff --git a/lib/sasl/test/systools_SUITE_data/d_bad_mod+warn/lib/fe-3.1/ebin/fe.appup b/lib/sasl/test/systools_SUITE_data/d_bad_mod+warn/lib/fe-3.1/ebin/fe.appup
new file mode 100644
index 0000000000..2db69eba76
--- /dev/null
+++ b/lib/sasl/test/systools_SUITE_data/d_bad_mod+warn/lib/fe-3.1/ebin/fe.appup
@@ -0,0 +1,27 @@
+%%
+%% Release upgrade script for fe (front end)
+%%
+
+{
+ "3.1",
+%%% Upgrade from:
+ [
+ {"2.1.1", [{update, fe1, soft, soft_purge, soft_purge, []},
+ {update, fe2, soft, soft_purge, soft_purge, [fe1]},
+ {update, fe3, {advanced, extra}, soft_purge, soft_purge,
+ [fe1, fe2]}
+ ]},
+
+ {"2.1", [{update, fe1, soft, soft_purge, soft_purge, []},
+ {update, fe2, soft, soft_purge, soft_purge, [fe1]},
+ {update, fe3, {advanced, extra}, soft_purge, soft_purge, [fe1, fe2]}
+ ]}
+ ],
+
+%%% Downgrade to:
+ [
+ {"2.1", [{update, fe2, soft, soft_purge, soft_purge, []},
+ {update, fe3, {advanced, extra}, soft_purge, soft_purge, [fe2]}
+ ]}
+ ]
+}.
diff --git a/lib/sasl/test/systools_SUITE_data/d_bad_mod+warn/lib/fe-3.1/src/fe1.erl b/lib/sasl/test/systools_SUITE_data/d_bad_mod+warn/lib/fe-3.1/src/fe1.erl
new file mode 100644
index 0000000000..cdbb9ef532
--- /dev/null
+++ b/lib/sasl/test/systools_SUITE_data/d_bad_mod+warn/lib/fe-3.1/src/fe1.erl
@@ -0,0 +1,2 @@
+-module(fe1).
+-vsn("1.1").
diff --git a/lib/sasl/test/systools_SUITE_data/d_bad_mod+warn/lib/fe-3.1/src/fe2.erl b/lib/sasl/test/systools_SUITE_data/d_bad_mod+warn/lib/fe-3.1/src/fe2.erl
new file mode 100644
index 0000000000..869f3b93c8
--- /dev/null
+++ b/lib/sasl/test/systools_SUITE_data/d_bad_mod+warn/lib/fe-3.1/src/fe2.erl
@@ -0,0 +1,2 @@
+-module(fe2).
+-vsn("1.0").
diff --git a/lib/sasl/test/systools_SUITE_data/d_bad_mod+warn/lib/fe-3.1/src/fe3.erl b/lib/sasl/test/systools_SUITE_data/d_bad_mod+warn/lib/fe-3.1/src/fe3.erl
new file mode 100644
index 0000000000..6473342f52
--- /dev/null
+++ b/lib/sasl/test/systools_SUITE_data/d_bad_mod+warn/lib/fe-3.1/src/fe3.erl
@@ -0,0 +1,2 @@
+-module(fe3).
+-vsn("2.0").
diff --git a/lib/sasl/test/systools_SUITE_data/d_links/lib/db-2.1/ebin/db.app b/lib/sasl/test/systools_SUITE_data/d_links/lib/db-2.1/ebin/db.app
new file mode 100644
index 0000000000..3e0ac3c3c9
--- /dev/null
+++ b/lib/sasl/test/systools_SUITE_data/d_links/lib/db-2.1/ebin/db.app
@@ -0,0 +1,8 @@
+{application, db,
+ [{description, "ERICSSON NR FOR DB"},
+ {vsn, "2.1"},
+ {modules, [{db1, "1.0"}, {db2, "1.0"}, {db3, "2.0"}]},
+ {registered, []},
+ {applications, []},
+ {env, []},
+ {start, {db1, start, []}}]}.
diff --git a/lib/sasl/test/systools_SUITE_data/d_links/lib/db-2.1/ebin/db.appup b/lib/sasl/test/systools_SUITE_data/d_links/lib/db-2.1/ebin/db.appup
new file mode 100644
index 0000000000..621838f0b4
--- /dev/null
+++ b/lib/sasl/test/systools_SUITE_data/d_links/lib/db-2.1/ebin/db.appup
@@ -0,0 +1,20 @@
+%%
+%% Release upgrade script for db (data base)
+%%
+
+{
+ "2.1",
+%%% Upgrade from:
+ [
+ {"1.0", [{update, db1, soft, soft_purge, soft_purge, []},
+ {update, db2, soft, soft_purge, soft_purge, [db1]}]},
+ {"1.1", [{update, db1, soft, soft_purge, soft_purge, []},
+ {update, db2, soft, soft_purge, soft_purge, [db1]}]}
+ ],
+
+%%% Downgrade to:
+ [
+ {"1.0", [{update, db1, soft, soft_purge, soft_purge, []},
+ {update, db2, soft, soft_purge, soft_purge, [db1]}]}
+ ]
+}.
diff --git a/lib/sasl/test/systools_SUITE_data/d_links/lib/db-2.1/src/db2.erl b/lib/sasl/test/systools_SUITE_data/d_links/lib/db-2.1/src/db2.erl
new file mode 100644
index 0000000000..a17640316e
--- /dev/null
+++ b/lib/sasl/test/systools_SUITE_data/d_links/lib/db-2.1/src/db2.erl
@@ -0,0 +1,2 @@
+-module(db2).
+-vsn("1.0").
diff --git a/lib/sasl/test/systools_SUITE_data/d_links/lib/db-2.1/src/db3.erl b/lib/sasl/test/systools_SUITE_data/d_links/lib/db-2.1/src/db3.erl
new file mode 100644
index 0000000000..6473342f52
--- /dev/null
+++ b/lib/sasl/test/systools_SUITE_data/d_links/lib/db-2.1/src/db3.erl
@@ -0,0 +1,2 @@
+-module(fe3).
+-vsn("2.0").
diff --git a/lib/sasl/test/systools_SUITE_data/d_links/lib/fe-3.1/ebin/fe.appup b/lib/sasl/test/systools_SUITE_data/d_links/lib/fe-3.1/ebin/fe.appup
new file mode 100644
index 0000000000..4ab13c1bae
--- /dev/null
+++ b/lib/sasl/test/systools_SUITE_data/d_links/lib/fe-3.1/ebin/fe.appup
@@ -0,0 +1,27 @@
+%%
+%% Release upgrade script for fe (front end)
+%%
+
+{
+ "3.1",
+%%% Upgrade from:
+ [
+ {"2.1.1", [{update, fe1, soft, soft_purge, soft_purge, []},
+ {update, fe2, soft, soft_purge, soft_purge, [fe1]},
+ {update, fe3, {{advanced, extra}, soft_purge, soft_purge,
+ [fe1, fe2]}
+ ]},
+
+ {"2.1", [{update, fe1, soft, soft_purge, soft_purge, []},
+ {update, fe2, soft, soft_purge, soft_purge, [fe1]},
+ {update, fe3, {advanced, extra}, soft_purge, soft_purge, [fe1, fe2]}
+ ]}
+ ],
+
+%%% Downgrade to:
+ [
+ {"2.1", [{update, fe2, soft, soft_purge, soft_purge, []},
+ {update, fe3, {advanced, extra}, soft_purge, soft_purge, [fe2]}
+ ]}
+ ]
+}.
diff --git a/lib/sasl/test/systools_SUITE_data/d_links/lib/fe-3.1/src/fe1.erl b/lib/sasl/test/systools_SUITE_data/d_links/lib/fe-3.1/src/fe1.erl
new file mode 100644
index 0000000000..aa5bfa8098
--- /dev/null
+++ b/lib/sasl/test/systools_SUITE_data/d_links/lib/fe-3.1/src/fe1.erl
@@ -0,0 +1,2 @@
+-module(fe1).
+-vsn("1.0").
diff --git a/lib/sasl/test/systools_SUITE_data/d_links/lib/fe-3.1/src/fe2.erl b/lib/sasl/test/systools_SUITE_data/d_links/lib/fe-3.1/src/fe2.erl
new file mode 100644
index 0000000000..869f3b93c8
--- /dev/null
+++ b/lib/sasl/test/systools_SUITE_data/d_links/lib/fe-3.1/src/fe2.erl
@@ -0,0 +1,2 @@
+-module(fe2).
+-vsn("1.0").
diff --git a/lib/sasl/test/systools_SUITE_data/d_links/lib/fe-3.1/src/fe3.erl b/lib/sasl/test/systools_SUITE_data/d_links/lib/fe-3.1/src/fe3.erl
new file mode 100644
index 0000000000..6473342f52
--- /dev/null
+++ b/lib/sasl/test/systools_SUITE_data/d_links/lib/fe-3.1/src/fe3.erl
@@ -0,0 +1,2 @@
+-module(fe3).
+-vsn("2.0").
diff --git a/lib/sasl/test/systools_SUITE_data/d_missing_src/lib/db-2.1/ebin/db.app b/lib/sasl/test/systools_SUITE_data/d_missing_src/lib/db-2.1/ebin/db.app
new file mode 100644
index 0000000000..191919f8d3
--- /dev/null
+++ b/lib/sasl/test/systools_SUITE_data/d_missing_src/lib/db-2.1/ebin/db.app
@@ -0,0 +1,8 @@
+{application, db,
+ [{description, "ERICSSON NR FOR DB"},
+ {vsn, "2.1"},
+ {modules, [{db1, "1.0"}, {db2, "1.0"}]},
+ {registered, []},
+ {applications, []},
+ {env, []},
+ {start, {db1, start, []}}]}.
diff --git a/lib/sasl/test/systools_SUITE_data/d_missing_src/lib/db-2.1/ebin/db.appup b/lib/sasl/test/systools_SUITE_data/d_missing_src/lib/db-2.1/ebin/db.appup
new file mode 100644
index 0000000000..621838f0b4
--- /dev/null
+++ b/lib/sasl/test/systools_SUITE_data/d_missing_src/lib/db-2.1/ebin/db.appup
@@ -0,0 +1,20 @@
+%%
+%% Release upgrade script for db (data base)
+%%
+
+{
+ "2.1",
+%%% Upgrade from:
+ [
+ {"1.0", [{update, db1, soft, soft_purge, soft_purge, []},
+ {update, db2, soft, soft_purge, soft_purge, [db1]}]},
+ {"1.1", [{update, db1, soft, soft_purge, soft_purge, []},
+ {update, db2, soft, soft_purge, soft_purge, [db1]}]}
+ ],
+
+%%% Downgrade to:
+ [
+ {"1.0", [{update, db1, soft, soft_purge, soft_purge, []},
+ {update, db2, soft, soft_purge, soft_purge, [db1]}]}
+ ]
+}.
diff --git a/lib/sasl/test/systools_SUITE_data/d_missing_src/lib/db-2.1/src/db1.erl b/lib/sasl/test/systools_SUITE_data/d_missing_src/lib/db-2.1/src/db1.erl
new file mode 100644
index 0000000000..a17640316e
--- /dev/null
+++ b/lib/sasl/test/systools_SUITE_data/d_missing_src/lib/db-2.1/src/db1.erl
@@ -0,0 +1,2 @@
+-module(db2).
+-vsn("1.0").
diff --git a/lib/sasl/test/systools_SUITE_data/d_missing_src/lib/db-2.1/src/db2.erl b/lib/sasl/test/systools_SUITE_data/d_missing_src/lib/db-2.1/src/db2.erl
new file mode 100644
index 0000000000..a17640316e
--- /dev/null
+++ b/lib/sasl/test/systools_SUITE_data/d_missing_src/lib/db-2.1/src/db2.erl
@@ -0,0 +1,2 @@
+-module(db2).
+-vsn("1.0").
diff --git a/lib/sasl/test/systools_SUITE_data/d_missing_src/lib/fe-3.1/ebin/fe.app b/lib/sasl/test/systools_SUITE_data/d_missing_src/lib/fe-3.1/ebin/fe.app
new file mode 100644
index 0000000000..d3bd85cda6
--- /dev/null
+++ b/lib/sasl/test/systools_SUITE_data/d_missing_src/lib/fe-3.1/ebin/fe.app
@@ -0,0 +1,8 @@
+{application, fe,
+ [{description, "ERICSSON NR FOR FE"},
+ {vsn, "3.1"},
+ {modules, [{fe1, "1.0"}, {fe2, "1.0"}, {fe3, "2.0"}]},
+ {registered, []},
+ {applications, []},
+ {env, []},
+ {start, {fe2, start, []}}]}.
diff --git a/lib/sasl/test/systools_SUITE_data/d_missing_src/lib/fe-3.1/ebin/fe.appup b/lib/sasl/test/systools_SUITE_data/d_missing_src/lib/fe-3.1/ebin/fe.appup
new file mode 100644
index 0000000000..4ab13c1bae
--- /dev/null
+++ b/lib/sasl/test/systools_SUITE_data/d_missing_src/lib/fe-3.1/ebin/fe.appup
@@ -0,0 +1,27 @@
+%%
+%% Release upgrade script for fe (front end)
+%%
+
+{
+ "3.1",
+%%% Upgrade from:
+ [
+ {"2.1.1", [{update, fe1, soft, soft_purge, soft_purge, []},
+ {update, fe2, soft, soft_purge, soft_purge, [fe1]},
+ {update, fe3, {{advanced, extra}, soft_purge, soft_purge,
+ [fe1, fe2]}
+ ]},
+
+ {"2.1", [{update, fe1, soft, soft_purge, soft_purge, []},
+ {update, fe2, soft, soft_purge, soft_purge, [fe1]},
+ {update, fe3, {advanced, extra}, soft_purge, soft_purge, [fe1, fe2]}
+ ]}
+ ],
+
+%%% Downgrade to:
+ [
+ {"2.1", [{update, fe2, soft, soft_purge, soft_purge, []},
+ {update, fe3, {advanced, extra}, soft_purge, soft_purge, [fe2]}
+ ]}
+ ]
+}.
diff --git a/lib/sasl/test/systools_SUITE_data/d_missing_src/lib/fe-3.1/src/fe1.erl b/lib/sasl/test/systools_SUITE_data/d_missing_src/lib/fe-3.1/src/fe1.erl
new file mode 100644
index 0000000000..cdbb9ef532
--- /dev/null
+++ b/lib/sasl/test/systools_SUITE_data/d_missing_src/lib/fe-3.1/src/fe1.erl
@@ -0,0 +1,2 @@
+-module(fe1).
+-vsn("1.1").
diff --git a/lib/sasl/test/systools_SUITE_data/d_missing_src/lib/fe-3.1/src/fe2.erl b/lib/sasl/test/systools_SUITE_data/d_missing_src/lib/fe-3.1/src/fe2.erl
new file mode 100644
index 0000000000..869f3b93c8
--- /dev/null
+++ b/lib/sasl/test/systools_SUITE_data/d_missing_src/lib/fe-3.1/src/fe2.erl
@@ -0,0 +1,2 @@
+-module(fe2).
+-vsn("1.0").
diff --git a/lib/sasl/test/systools_SUITE_data/d_missing_src/lib/fe-3.1/src/fe3.erl b/lib/sasl/test/systools_SUITE_data/d_missing_src/lib/fe-3.1/src/fe3.erl
new file mode 100644
index 0000000000..6473342f52
--- /dev/null
+++ b/lib/sasl/test/systools_SUITE_data/d_missing_src/lib/fe-3.1/src/fe3.erl
@@ -0,0 +1,2 @@
+-module(fe3).
+-vsn("2.0").
diff --git a/lib/sasl/test/systools_SUITE_data/d_no_appup/lib/fe-2.1/ebin/fe.app b/lib/sasl/test/systools_SUITE_data/d_no_appup/lib/fe-2.1/ebin/fe.app
new file mode 100644
index 0000000000..47ea248720
--- /dev/null
+++ b/lib/sasl/test/systools_SUITE_data/d_no_appup/lib/fe-2.1/ebin/fe.app
@@ -0,0 +1,8 @@
+{application, fe,
+ [{description, "ERICSSON NR FOR FE"},
+ {vsn, "2.1"},
+ {modules, [{fe1, "1.0"}, {fe2, "1.0"}, {fe3, "2.0"}]},
+ {registered, []},
+ {applications, []},
+ {env, []},
+ {start, {fe2, start, []}}]}.
diff --git a/lib/sasl/test/systools_SUITE_data/d_no_appup/lib/fe-2.1/ebin/fe.appup b/lib/sasl/test/systools_SUITE_data/d_no_appup/lib/fe-2.1/ebin/fe.appup
new file mode 100644
index 0000000000..2db69eba76
--- /dev/null
+++ b/lib/sasl/test/systools_SUITE_data/d_no_appup/lib/fe-2.1/ebin/fe.appup
@@ -0,0 +1,27 @@
+%%
+%% Release upgrade script for fe (front end)
+%%
+
+{
+ "3.1",
+%%% Upgrade from:
+ [
+ {"2.1.1", [{update, fe1, soft, soft_purge, soft_purge, []},
+ {update, fe2, soft, soft_purge, soft_purge, [fe1]},
+ {update, fe3, {advanced, extra}, soft_purge, soft_purge,
+ [fe1, fe2]}
+ ]},
+
+ {"2.1", [{update, fe1, soft, soft_purge, soft_purge, []},
+ {update, fe2, soft, soft_purge, soft_purge, [fe1]},
+ {update, fe3, {advanced, extra}, soft_purge, soft_purge, [fe1, fe2]}
+ ]}
+ ],
+
+%%% Downgrade to:
+ [
+ {"2.1", [{update, fe2, soft, soft_purge, soft_purge, []},
+ {update, fe3, {advanced, extra}, soft_purge, soft_purge, [fe2]}
+ ]}
+ ]
+}.
diff --git a/lib/sasl/test/systools_SUITE_data/d_no_appup/lib/fe-3.1/ebin/fe.app b/lib/sasl/test/systools_SUITE_data/d_no_appup/lib/fe-3.1/ebin/fe.app
new file mode 100644
index 0000000000..0696e2494c
--- /dev/null
+++ b/lib/sasl/test/systools_SUITE_data/d_no_appup/lib/fe-3.1/ebin/fe.app
@@ -0,0 +1,7 @@
+{application, fe,
+ [{description, "ERICSSON NR FOR FE"},
+ {vsn, "3.1"},
+ {modules, [{fe1, "1.0"}, {fe2, "1.0"}, {fe3, "2.0"}]},
+ {registered, []},
+ {applications, []},
+ {mod, {fe1, []}}]}.
diff --git a/lib/sasl/test/systools_SUITE_data/d_normal/lib/db-1.0/ebin/db.app b/lib/sasl/test/systools_SUITE_data/d_normal/lib/db-1.0/ebin/db.app
new file mode 100644
index 0000000000..22530ee335
--- /dev/null
+++ b/lib/sasl/test/systools_SUITE_data/d_normal/lib/db-1.0/ebin/db.app
@@ -0,0 +1,8 @@
+{application, db,
+ [{description, "ERICSSON NR FOR DB"},
+ {vsn, "1.0"},
+ {modules, [{db1, "1.0"}, {db2, "1.0"}]},
+ {registered, []},
+ {applications, []},
+ {env, []},
+ {start, {db1, start, []}}]}.
diff --git a/lib/sasl/test/systools_SUITE_data/d_normal/lib/db-1.0/src/db1.erl b/lib/sasl/test/systools_SUITE_data/d_normal/lib/db-1.0/src/db1.erl
new file mode 100644
index 0000000000..a17640316e
--- /dev/null
+++ b/lib/sasl/test/systools_SUITE_data/d_normal/lib/db-1.0/src/db1.erl
@@ -0,0 +1,2 @@
+-module(db2).
+-vsn("1.0").
diff --git a/lib/sasl/test/systools_SUITE_data/d_normal/lib/db-1.0/src/db2.erl b/lib/sasl/test/systools_SUITE_data/d_normal/lib/db-1.0/src/db2.erl
new file mode 100644
index 0000000000..a17640316e
--- /dev/null
+++ b/lib/sasl/test/systools_SUITE_data/d_normal/lib/db-1.0/src/db2.erl
@@ -0,0 +1,2 @@
+-module(db2).
+-vsn("1.0").
diff --git a/lib/sasl/test/systools_SUITE_data/d_normal/lib/db-1.1/ebin/db.app b/lib/sasl/test/systools_SUITE_data/d_normal/lib/db-1.1/ebin/db.app
new file mode 100644
index 0000000000..7243a0a96a
--- /dev/null
+++ b/lib/sasl/test/systools_SUITE_data/d_normal/lib/db-1.1/ebin/db.app
@@ -0,0 +1,8 @@
+{application, db,
+ [{description, "ERICSSON NR FOR DB"},
+ {vsn, "1.1"},
+ {modules, [{db1, "1.0"}, {db2, "1.0"}]},
+ {registered, []},
+ {applications, []},
+ {env, []},
+ {start, {db1, start, []}}]}.
diff --git a/lib/sasl/test/systools_SUITE_data/d_normal/lib/db-1.1/src/db1.erl b/lib/sasl/test/systools_SUITE_data/d_normal/lib/db-1.1/src/db1.erl
new file mode 100644
index 0000000000..a17640316e
--- /dev/null
+++ b/lib/sasl/test/systools_SUITE_data/d_normal/lib/db-1.1/src/db1.erl
@@ -0,0 +1,2 @@
+-module(db2).
+-vsn("1.0").
diff --git a/lib/sasl/test/systools_SUITE_data/d_normal/lib/db-1.1/src/db2.erl b/lib/sasl/test/systools_SUITE_data/d_normal/lib/db-1.1/src/db2.erl
new file mode 100644
index 0000000000..a17640316e
--- /dev/null
+++ b/lib/sasl/test/systools_SUITE_data/d_normal/lib/db-1.1/src/db2.erl
@@ -0,0 +1,2 @@
+-module(db2).
+-vsn("1.0").
diff --git a/lib/sasl/test/systools_SUITE_data/d_normal/lib/db-2.1/ebin/db.app b/lib/sasl/test/systools_SUITE_data/d_normal/lib/db-2.1/ebin/db.app
new file mode 100644
index 0000000000..202d7f1234
--- /dev/null
+++ b/lib/sasl/test/systools_SUITE_data/d_normal/lib/db-2.1/ebin/db.app
@@ -0,0 +1,7 @@
+{application, db,
+ [{description, "ERICSSON NR FOR DB"},
+ {vsn, "2.1"},
+ {modules, [{db1, "1.0"}, {db2, "1.0"}]},
+ {registered, []},
+ {applications, []},
+ {mod, {db1, []}}]}.
diff --git a/lib/sasl/test/systools_SUITE_data/d_normal/lib/db-2.1/ebin/db.appup b/lib/sasl/test/systools_SUITE_data/d_normal/lib/db-2.1/ebin/db.appup
new file mode 100644
index 0000000000..621838f0b4
--- /dev/null
+++ b/lib/sasl/test/systools_SUITE_data/d_normal/lib/db-2.1/ebin/db.appup
@@ -0,0 +1,20 @@
+%%
+%% Release upgrade script for db (data base)
+%%
+
+{
+ "2.1",
+%%% Upgrade from:
+ [
+ {"1.0", [{update, db1, soft, soft_purge, soft_purge, []},
+ {update, db2, soft, soft_purge, soft_purge, [db1]}]},
+ {"1.1", [{update, db1, soft, soft_purge, soft_purge, []},
+ {update, db2, soft, soft_purge, soft_purge, [db1]}]}
+ ],
+
+%%% Downgrade to:
+ [
+ {"1.0", [{update, db1, soft, soft_purge, soft_purge, []},
+ {update, db2, soft, soft_purge, soft_purge, [db1]}]}
+ ]
+}.
diff --git a/lib/sasl/test/systools_SUITE_data/d_normal/lib/db-2.1/src/db1.erl b/lib/sasl/test/systools_SUITE_data/d_normal/lib/db-2.1/src/db1.erl
new file mode 100644
index 0000000000..7cddf6bb63
--- /dev/null
+++ b/lib/sasl/test/systools_SUITE_data/d_normal/lib/db-2.1/src/db1.erl
@@ -0,0 +1,13 @@
+-module(db1).
+-vsn("1.0").
+
+-export([a/0]).
+
+a() ->
+ lists:non_existing_func("dummy_list"),
+ b().
+
+b() ->
+ fe2:non_existing_func(),
+ db2:non_existing_func(),
+ fe1:a().
diff --git a/lib/sasl/test/systools_SUITE_data/d_normal/lib/db-2.1/src/db2.erl b/lib/sasl/test/systools_SUITE_data/d_normal/lib/db-2.1/src/db2.erl
new file mode 100644
index 0000000000..a17640316e
--- /dev/null
+++ b/lib/sasl/test/systools_SUITE_data/d_normal/lib/db-2.1/src/db2.erl
@@ -0,0 +1,2 @@
+-module(db2).
+-vsn("1.0").
diff --git a/lib/sasl/test/systools_SUITE_data/d_normal/lib/db-3.1/ebin/db.app b/lib/sasl/test/systools_SUITE_data/d_normal/lib/db-3.1/ebin/db.app
new file mode 100644
index 0000000000..97643561eb
--- /dev/null
+++ b/lib/sasl/test/systools_SUITE_data/d_normal/lib/db-3.1/ebin/db.app
@@ -0,0 +1,7 @@
+{application, db,
+ [{description, "ERICSSON NR FOR DB"},
+ {vsn, "3.1"},
+ {modules, [db1, db2]},
+ {registered, []},
+ {applications, []},
+ {mod, {db1, []}}]}.
diff --git a/lib/sasl/test/systools_SUITE_data/d_normal/lib/db-3.1/ebin/db.appup b/lib/sasl/test/systools_SUITE_data/d_normal/lib/db-3.1/ebin/db.appup
new file mode 100644
index 0000000000..e636eee158
--- /dev/null
+++ b/lib/sasl/test/systools_SUITE_data/d_normal/lib/db-3.1/ebin/db.appup
@@ -0,0 +1,20 @@
+%%
+%% Release upgrade script for db (data base)
+%%
+
+{
+ "3.1",
+%%% Upgrade from:
+ [
+ {"1.0", [{update, db1, soft, soft_purge, soft_purge, []},
+ {update, db2, soft, soft_purge, soft_purge, [db1]}]},
+ {"1.1", [{update, db1, soft, soft_purge, soft_purge, []},
+ {update, db2, soft, soft_purge, soft_purge, [db1]}]}
+ ],
+
+%%% Downgrade to:
+ [
+ {"1.0", [{update, db1, soft, soft_purge, soft_purge, []},
+ {update, db2, soft, soft_purge, soft_purge, [db1]}]}
+ ]
+}.
diff --git a/lib/sasl/test/systools_SUITE_data/d_normal/lib/db-3.1/src/db1.erl b/lib/sasl/test/systools_SUITE_data/d_normal/lib/db-3.1/src/db1.erl
new file mode 100644
index 0000000000..a17640316e
--- /dev/null
+++ b/lib/sasl/test/systools_SUITE_data/d_normal/lib/db-3.1/src/db1.erl
@@ -0,0 +1,2 @@
+-module(db2).
+-vsn("1.0").
diff --git a/lib/sasl/test/systools_SUITE_data/d_normal/lib/db-3.1/src/db2.erl b/lib/sasl/test/systools_SUITE_data/d_normal/lib/db-3.1/src/db2.erl
new file mode 100644
index 0000000000..a17640316e
--- /dev/null
+++ b/lib/sasl/test/systools_SUITE_data/d_normal/lib/db-3.1/src/db2.erl
@@ -0,0 +1,2 @@
+-module(db2).
+-vsn("1.0").
diff --git a/lib/sasl/test/systools_SUITE_data/d_normal/lib/fe-2.1.1/ebin/fe.app b/lib/sasl/test/systools_SUITE_data/d_normal/lib/fe-2.1.1/ebin/fe.app
new file mode 100644
index 0000000000..c7ba1dfe91
--- /dev/null
+++ b/lib/sasl/test/systools_SUITE_data/d_normal/lib/fe-2.1.1/ebin/fe.app
@@ -0,0 +1,8 @@
+{application, fe,
+ [{description, "ERICSSON NR FOR FE"},
+ {vsn, "2.1.1"},
+ {modules, [{fe1, "1.0"}, {fe2, "1.0"}, {fe3, "2.0"}]},
+ {registered, []},
+ {applications, []},
+ {env, []},
+ {start, {fe2, start, []}}]}.
diff --git a/lib/sasl/test/systools_SUITE_data/d_normal/lib/fe-2.1.1/src/fe1.erl b/lib/sasl/test/systools_SUITE_data/d_normal/lib/fe-2.1.1/src/fe1.erl
new file mode 100644
index 0000000000..aa5bfa8098
--- /dev/null
+++ b/lib/sasl/test/systools_SUITE_data/d_normal/lib/fe-2.1.1/src/fe1.erl
@@ -0,0 +1,2 @@
+-module(fe1).
+-vsn("1.0").
diff --git a/lib/sasl/test/systools_SUITE_data/d_normal/lib/fe-2.1.1/src/fe2.erl b/lib/sasl/test/systools_SUITE_data/d_normal/lib/fe-2.1.1/src/fe2.erl
new file mode 100644
index 0000000000..869f3b93c8
--- /dev/null
+++ b/lib/sasl/test/systools_SUITE_data/d_normal/lib/fe-2.1.1/src/fe2.erl
@@ -0,0 +1,2 @@
+-module(fe2).
+-vsn("1.0").
diff --git a/lib/sasl/test/systools_SUITE_data/d_normal/lib/fe-2.1.1/src/fe3.erl b/lib/sasl/test/systools_SUITE_data/d_normal/lib/fe-2.1.1/src/fe3.erl
new file mode 100644
index 0000000000..6473342f52
--- /dev/null
+++ b/lib/sasl/test/systools_SUITE_data/d_normal/lib/fe-2.1.1/src/fe3.erl
@@ -0,0 +1,2 @@
+-module(fe3).
+-vsn("2.0").
diff --git a/lib/sasl/test/systools_SUITE_data/d_normal/lib/fe-2.1/ebin/fe.app b/lib/sasl/test/systools_SUITE_data/d_normal/lib/fe-2.1/ebin/fe.app
new file mode 100644
index 0000000000..47ea248720
--- /dev/null
+++ b/lib/sasl/test/systools_SUITE_data/d_normal/lib/fe-2.1/ebin/fe.app
@@ -0,0 +1,8 @@
+{application, fe,
+ [{description, "ERICSSON NR FOR FE"},
+ {vsn, "2.1"},
+ {modules, [{fe1, "1.0"}, {fe2, "1.0"}, {fe3, "2.0"}]},
+ {registered, []},
+ {applications, []},
+ {env, []},
+ {start, {fe2, start, []}}]}.
diff --git a/lib/sasl/test/systools_SUITE_data/d_normal/lib/fe-2.1/src/fe1.erl b/lib/sasl/test/systools_SUITE_data/d_normal/lib/fe-2.1/src/fe1.erl
new file mode 100644
index 0000000000..aa5bfa8098
--- /dev/null
+++ b/lib/sasl/test/systools_SUITE_data/d_normal/lib/fe-2.1/src/fe1.erl
@@ -0,0 +1,2 @@
+-module(fe1).
+-vsn("1.0").
diff --git a/lib/sasl/test/systools_SUITE_data/d_normal/lib/fe-2.1/src/fe2.erl b/lib/sasl/test/systools_SUITE_data/d_normal/lib/fe-2.1/src/fe2.erl
new file mode 100644
index 0000000000..869f3b93c8
--- /dev/null
+++ b/lib/sasl/test/systools_SUITE_data/d_normal/lib/fe-2.1/src/fe2.erl
@@ -0,0 +1,2 @@
+-module(fe2).
+-vsn("1.0").
diff --git a/lib/sasl/test/systools_SUITE_data/d_normal/lib/fe-2.1/src/fe3.erl b/lib/sasl/test/systools_SUITE_data/d_normal/lib/fe-2.1/src/fe3.erl
new file mode 100644
index 0000000000..6473342f52
--- /dev/null
+++ b/lib/sasl/test/systools_SUITE_data/d_normal/lib/fe-2.1/src/fe3.erl
@@ -0,0 +1,2 @@
+-module(fe3).
+-vsn("2.0").
diff --git a/lib/sasl/test/systools_SUITE_data/d_normal/lib/fe-3.1/ebin/fe.app b/lib/sasl/test/systools_SUITE_data/d_normal/lib/fe-3.1/ebin/fe.app
new file mode 100644
index 0000000000..0696e2494c
--- /dev/null
+++ b/lib/sasl/test/systools_SUITE_data/d_normal/lib/fe-3.1/ebin/fe.app
@@ -0,0 +1,7 @@
+{application, fe,
+ [{description, "ERICSSON NR FOR FE"},
+ {vsn, "3.1"},
+ {modules, [{fe1, "1.0"}, {fe2, "1.0"}, {fe3, "2.0"}]},
+ {registered, []},
+ {applications, []},
+ {mod, {fe1, []}}]}.
diff --git a/lib/sasl/test/systools_SUITE_data/d_normal/lib/fe-3.1/ebin/fe.appup b/lib/sasl/test/systools_SUITE_data/d_normal/lib/fe-3.1/ebin/fe.appup
new file mode 100644
index 0000000000..2db69eba76
--- /dev/null
+++ b/lib/sasl/test/systools_SUITE_data/d_normal/lib/fe-3.1/ebin/fe.appup
@@ -0,0 +1,27 @@
+%%
+%% Release upgrade script for fe (front end)
+%%
+
+{
+ "3.1",
+%%% Upgrade from:
+ [
+ {"2.1.1", [{update, fe1, soft, soft_purge, soft_purge, []},
+ {update, fe2, soft, soft_purge, soft_purge, [fe1]},
+ {update, fe3, {advanced, extra}, soft_purge, soft_purge,
+ [fe1, fe2]}
+ ]},
+
+ {"2.1", [{update, fe1, soft, soft_purge, soft_purge, []},
+ {update, fe2, soft, soft_purge, soft_purge, [fe1]},
+ {update, fe3, {advanced, extra}, soft_purge, soft_purge, [fe1, fe2]}
+ ]}
+ ],
+
+%%% Downgrade to:
+ [
+ {"2.1", [{update, fe2, soft, soft_purge, soft_purge, []},
+ {update, fe3, {advanced, extra}, soft_purge, soft_purge, [fe2]}
+ ]}
+ ]
+}.
diff --git a/lib/sasl/test/systools_SUITE_data/d_normal/lib/fe-3.1/src/fe1.erl b/lib/sasl/test/systools_SUITE_data/d_normal/lib/fe-3.1/src/fe1.erl
new file mode 100644
index 0000000000..8aca89b2c7
--- /dev/null
+++ b/lib/sasl/test/systools_SUITE_data/d_normal/lib/fe-3.1/src/fe1.erl
@@ -0,0 +1,7 @@
+-module(fe1).
+-vsn("1.0").
+
+-export([a/0]).
+
+a() ->
+ ok.
diff --git a/lib/sasl/test/systools_SUITE_data/d_normal/lib/fe-3.1/src/fe2.erl b/lib/sasl/test/systools_SUITE_data/d_normal/lib/fe-3.1/src/fe2.erl
new file mode 100644
index 0000000000..869f3b93c8
--- /dev/null
+++ b/lib/sasl/test/systools_SUITE_data/d_normal/lib/fe-3.1/src/fe2.erl
@@ -0,0 +1,2 @@
+-module(fe2).
+-vsn("1.0").
diff --git a/lib/sasl/test/systools_SUITE_data/d_normal/lib/fe-3.1/src/fe3.erl b/lib/sasl/test/systools_SUITE_data/d_normal/lib/fe-3.1/src/fe3.erl
new file mode 100644
index 0000000000..6473342f52
--- /dev/null
+++ b/lib/sasl/test/systools_SUITE_data/d_normal/lib/fe-3.1/src/fe3.erl
@@ -0,0 +1,2 @@
+-module(fe3).
+-vsn("2.0").
diff --git a/lib/sasl/test/systools_SUITE_data/lib/kernel/ebin/kernel.app b/lib/sasl/test/systools_SUITE_data/lib/kernel/ebin/kernel.app
new file mode 100644
index 0000000000..e33314be7a
--- /dev/null
+++ b/lib/sasl/test/systools_SUITE_data/lib/kernel/ebin/kernel.app
@@ -0,0 +1,6 @@
+{application, kernel,
+ [{description, "FAKE KERNEL"},
+ {vsn, "1.0"},
+ {modules, []},
+ {registered, []},
+ {applications, []}]}.
diff --git a/lib/sasl/test/systools_SUITE_data/lib/kernel/ebin/kernel.appup b/lib/sasl/test/systools_SUITE_data/lib/kernel/ebin/kernel.appup
new file mode 100644
index 0000000000..c53f07591f
--- /dev/null
+++ b/lib/sasl/test/systools_SUITE_data/lib/kernel/ebin/kernel.appup
@@ -0,0 +1,12 @@
+%%
+%% Fake release upgrade script for kernel
+%%
+
+{
+ "4.4.1",
+ [
+ ],
+
+ [
+ ]
+}.
diff --git a/lib/sasl/test/systools_SUITE_data/lib/stdlib/ebin/stdlib.app b/lib/sasl/test/systools_SUITE_data/lib/stdlib/ebin/stdlib.app
new file mode 100644
index 0000000000..288fcfe74e
--- /dev/null
+++ b/lib/sasl/test/systools_SUITE_data/lib/stdlib/ebin/stdlib.app
@@ -0,0 +1,6 @@
+{application, stdlib,
+ [{description, "FAKE STDLIB"},
+ {vsn, "1.0"},
+ {modules, []},
+ {registered, []},
+ {applications, []}]}.
diff --git a/lib/sasl/test/systools_SUITE_data/lib/stdlib/ebin/stdlib.appup b/lib/sasl/test/systools_SUITE_data/lib/stdlib/ebin/stdlib.appup
new file mode 100644
index 0000000000..b521eb5d71
--- /dev/null
+++ b/lib/sasl/test/systools_SUITE_data/lib/stdlib/ebin/stdlib.appup
@@ -0,0 +1,12 @@
+%%
+%% Fake release upgrade script for stdlib
+%%
+
+{
+ "1.1",
+ [
+ ],
+
+ [
+ ]
+}.
diff --git a/lib/sasl/test/systools_rc_SUITE.erl b/lib/sasl/test/systools_rc_SUITE.erl
new file mode 100644
index 0000000000..bb93f38fa7
--- /dev/null
+++ b/lib/sasl/test/systools_rc_SUITE.erl
@@ -0,0 +1,488 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 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(systools_rc_SUITE).
+
+-include_lib("test_server/include/test_server.hrl").
+-include_lib("sasl/src/systools.hrl").
+-export([all/0,groups/0,init_per_group/2,end_per_group/2,
+ syntax_check/1, translate/1, translate_app/1]).
+
+%%-----------------------------------------------------------------
+%% erl -compile systools_rc_SUITE @i ../src/ @i ../../test_server/include/
+%% c(systools_rc_SUITE, [{i, "../src"}, {i, "../../test_server/include"}]).
+%%-----------------------------------------------------------------
+all() ->
+ [syntax_check, translate, translate_app].
+
+groups() ->
+ [].
+
+init_per_group(_GroupName, Config) ->
+ Config.
+
+end_per_group(_GroupName, Config) ->
+ Config.
+
+
+syntax_check(suite) -> [];
+syntax_check(Config) when is_list(Config) ->
+ PreApps =
+ [#application{name = test,
+ description = "TEST",
+ vsn = "0.1",
+ modules = [{foo,1},{bar,1},{baz,1},{old_mod,1}],
+ regs = [],
+ mod = {sasl, []}},
+ #application{name = snmp,
+ description = "SNMP",
+ vsn = "1.0",
+ modules = [snmp],
+ regs = [],
+ mod = {snmp, []}}],
+ Apps =
+ [#application{name = test,
+ description = "TEST",
+ vsn = "1.0",
+ modules = [{foo,1},{bar,1},{baz,1},{new_mod,1}],
+ regs = [],
+ mod = {sasl, []}}],
+ S1 = [
+ {update, bar, {advanced, extra}, brutal_purge, brutal_purge, []},
+ {update, foo, soft, soft_purge, soft_purge, [bar]},
+ {update, baz, 5000, soft, brutal_purge, brutal_purge, []},
+ {add_module, new_mod},
+ {remove_application, snmp}
+ ],
+ ?line {ok, _} = systools_rc:translate_scripts([S1], Apps, PreApps),
+ S2 = [
+ {apply, {m, f, [a]}},
+ {load_object_code, {tst, "1.0", [new_mod]}},
+ point_of_no_return,
+ {update, bar, {advanced, extra}, brutal_purge, brutal_purge, []},
+ {update, foo, soft, soft_purge, soft_purge, [bar]},
+ {load, {new_mod, soft_purge, soft_purge}},
+ {remove, {old_mod, soft_purge, soft_purge}},
+ {purge, [m1, m2]},
+ {suspend, [m1]},
+ {code_change, [{m1, extra}]},
+ {resume, [m1]},
+ {stop, [m3,m4]},
+ {start, [m3,m4]},
+ {sync_nodes, id1, {m, f, [a]}},
+ {sync_nodes, id2, [cp1, cp2]},
+ {apply, {m,f,[a]}},
+ restart_new_emulator
+ ],
+ ?line {ok, _} = systools_rc:translate_scripts([S2], Apps, []),
+ S3 = [{apply, {m, f, a}}],
+ ?line {error, _, _} = systools_rc:translate_scripts([S3], Apps, []),
+ S3_1 = [{apply, {m, 3, a}}],
+ ?line {error, _, _} = systools_rc:translate_scripts([S3_1], Apps, []),
+ S4 = [{apply, {m, f}}],
+ ?line {error, _, _} = systools_rc:translate_scripts([S4], Apps, []),
+ S5 = [{load_object_code, hej}],
+ ?line {error, _, _} = systools_rc:translate_scripts([S5], Apps, []),
+ S6 = [{load_object_code, {342, "1.0", [foo]}}],
+ ?line {error, _, _} = systools_rc:translate_scripts([S6], Apps, []),
+ S7 = [{load_object_code, {tets, "1.0", foo}}],
+ ?line {error, _, _} = systools_rc:translate_scripts([S7], Apps, []),
+ S8 = [{suspend, [m1]}, point_of_no_return],
+ ?line {error, _, _} = systools_rc:translate_scripts([S8], Apps, []),
+ S9 = [{update, ba, {advanced, extra}, brutal_purge, brutal_purge, []}],
+ ?line {error, _, _} = systools_rc:translate_scripts([S9], Apps, []),
+ S10 = [{update, bar, {advanced, extra}, brutal_purge, brutal_purge, [baz]}],
+ ?line {error, _, _} = systools_rc:translate_scripts([S10], Apps, []),
+ S11 = [{update, bar, {advanced, extra}, brutal_purge, brutal_purge, [ba]}],
+ ?line {error, _, _} = systools_rc:translate_scripts([S11], Apps, []),
+ S12 = [{update, bar, advanced, brutal_purge, brutal_purge, []}],
+ ?line {error, _, _} = systools_rc:translate_scripts([S12], Apps, []),
+ S13 = [{update, bar, {advanced, extra}, rutal_purge, brutal_purge, [ba]}],
+ ?line {error, _, _} = systools_rc:translate_scripts([S13], Apps, []),
+ S14 = [{update, bar, {advanced, extra}, brutal_purge, rutal_purge, [ba]}],
+ ?line {error, _, _} = systools_rc:translate_scripts([S14], Apps, []),
+ S15 = [{update, bar, {advanced, extra}, brutal_purge, brutal_purge, ba}],
+ ?line {error, _, _} = systools_rc:translate_scripts([S15], Apps, []),
+ S16 = [{code_change, [module]}],
+ ?line {error, _, _} = systools_rc:translate_scripts([S16], Apps, []),
+ ?line ok.
+
+translate(suite) -> [];
+translate(Config) when is_list(Config) ->
+ Apps =
+ [#application{name = test,
+ description = "TEST",
+ vsn = "1.0",
+ modules = [{foo,1},{bar,1},{baz,1},
+ {x,1},{y,1},{z,1}],
+ regs = [],
+ mod = {sasl, []}}],
+ %% Simple translation (1)
+ Up1 = [{update, foo, soft, soft_purge, soft_purge, []}],
+ ?line {ok, X1} = systools_rc:translate_scripts([Up1], Apps, []),
+ ?line [{load_object_code, {test,"1.0",[foo]}},
+ point_of_no_return,
+ {suspend,[foo]},
+ {load,{foo,soft_purge,soft_purge}},
+ {resume,[foo]}] = X1,
+
+ %% Simple translation (2)
+ Up2 = [{update, foo, {advanced, extra}, soft_purge, soft_purge, []}],
+ ?line {ok, X2} = systools_rc:translate_scripts([Up2], Apps, []),
+ ?line [{load_object_code, {test,"1.0",[foo]}},
+ point_of_no_return,
+ {suspend,[foo]},
+ {load,{foo,soft_purge,soft_purge}},
+ {code_change, up, [{foo, extra}]},
+ {resume,[foo]}] = X2,
+
+ ?line {ok, X22} = systools_rc:translate_scripts(dn,[Up2], Apps, []),
+ ?line [{load_object_code, {test,"1.0",[foo]}},
+ point_of_no_return,
+ {suspend,[foo]},
+ {code_change, down, [{foo, extra}]},
+ {load,{foo,soft_purge,soft_purge}},
+ {resume,[foo]}] = X22,
+
+ Up2a = [{update, foo, static, default, {advanced,extra},
+ soft_purge, soft_purge, []}],
+ ?line {ok, X2a} = systools_rc:translate_scripts([Up2a], Apps, []),
+ ?line [{load_object_code, {test,"1.0",[foo]}},
+ point_of_no_return,
+ {suspend,[foo]},
+ {load,{foo,soft_purge,soft_purge}},
+ {code_change, up, [{foo, extra}]},
+ {resume,[foo]}] = X2a,
+
+ ?line {ok, X22a} = systools_rc:translate_scripts(dn,[Up2a], Apps, []),
+ ?line [{load_object_code, {test,"1.0",[foo]}},
+ point_of_no_return,
+ {suspend,[foo]},
+ {load,{foo,soft_purge,soft_purge}},
+ {code_change, down, [{foo, extra}]},
+ {resume,[foo]}] = X22a,
+
+ %% Simple dependency (1)
+ Up3 = [{update, foo, soft, soft_purge, soft_purge, [bar]},
+ {update, bar, soft, soft_purge, soft_purge, []}],
+ ?line {ok, X31} = systools_rc:translate_scripts([Up3], Apps, []),
+ ?line [{load_object_code,{test,"1.0",[foo,bar]}},
+ point_of_no_return,
+ {suspend,[foo,bar]},
+ {load,{bar,soft_purge,soft_purge}},
+ {load,{foo,soft_purge,soft_purge}},
+ {resume,[bar,foo]}] = X31,
+ ?line {ok, X32} = systools_rc:translate_scripts(dn,[Up3], Apps, []),
+ ?line [{load_object_code,{test,"1.0",[foo,bar]}},
+ point_of_no_return,
+ {suspend,[foo,bar]},
+ {load,{foo,soft_purge,soft_purge}},
+ {load,{bar,soft_purge,soft_purge}},
+ {resume,[bar,foo]}] = X32,
+
+ Up3a = [{update, foo, static, default, soft, soft_purge, soft_purge, [bar]},
+ {update, bar, static, default, soft, soft_purge, soft_purge, []}],
+ ?line {ok, X3a1} = systools_rc:translate_scripts([Up3a], Apps, []),
+ ?line [{load_object_code,{test,"1.0",[foo,bar]}},
+ point_of_no_return,
+ {suspend,[foo, bar]},
+ {load,{bar,soft_purge,soft_purge}},
+ {load,{foo,soft_purge,soft_purge}},
+ {resume,[bar,foo]}] = X3a1,
+ ?line {ok, X3a2} = systools_rc:translate_scripts(dn,[Up3a], Apps, []),
+ ?line [{load_object_code,{test,"1.0",[foo,bar]}},
+ point_of_no_return,
+ {suspend,[foo,bar]},
+ {load,{foo,soft_purge,soft_purge}},
+ {load,{bar,soft_purge,soft_purge}},
+ {resume,[bar,foo]}] = X3a2,
+
+ %% Simple dependency (2)
+ Up4 = [{update, foo, soft, soft_purge, soft_purge, [bar]},
+ {update, bar, {advanced, []}, soft_purge, soft_purge, []}],
+ ?line {ok, X4} = systools_rc:translate_scripts(up,[Up4], Apps, []),
+ ?line [{load_object_code,{test,"1.0",[foo,bar]}},
+ point_of_no_return,
+ {suspend,[foo,bar]},
+ {load,{bar,soft_purge,soft_purge}},
+ {load,{foo,soft_purge,soft_purge}},
+ {code_change,up,[{bar,[]}]},
+ {resume,[bar,foo]}] = X4,
+
+ ?line {ok, X42} = systools_rc:translate_scripts(dn,[Up4], Apps, []),
+ ?line [{load_object_code,{test,"1.0",[foo,bar]}},
+ point_of_no_return,
+ {suspend,[foo,bar]},
+ {code_change,down,[{bar,[]}]},
+ {load,{foo,soft_purge,soft_purge}},
+ {load,{bar,soft_purge,soft_purge}},
+ {resume,[bar,foo]}] = X42,
+
+ Up4a = [{update, foo, soft, soft_purge, soft_purge, [bar]},
+ {update, bar, static, infinity, {advanced, []},
+ soft_purge, soft_purge, []}],
+ ?line {ok, X4a} = systools_rc:translate_scripts(up,[Up4a], Apps, []),
+ ?line [{load_object_code,{test,"1.0",[foo,bar]}},
+ point_of_no_return,
+ {suspend,[foo,{bar,infinity}]},
+ {load,{bar,soft_purge,soft_purge}},
+ {load,{foo,soft_purge,soft_purge}},
+ {code_change,up,[{bar,[]}]},
+ {resume,[bar,foo]}] = X4a,
+
+ ?line {ok, X42a} = systools_rc:translate_scripts(dn,[Up4a], Apps, []),
+ ?line [{load_object_code,{test,"1.0",[foo,bar]}},
+ point_of_no_return,
+ {suspend,[foo,{bar,infinity}]},
+ {load,{foo,soft_purge,soft_purge}},
+ {load,{bar,soft_purge,soft_purge}},
+ {code_change,down,[{bar,[]}]},
+ {resume,[bar,foo]}] = X42a,
+
+ Up4b = [{update, foo, soft, soft_purge, soft_purge, [bar]},
+ {update, bar, dynamic, infinity, {advanced, []},
+ soft_purge, soft_purge, []}],
+ ?line {ok, X4b} = systools_rc:translate_scripts(up,[Up4b], Apps, []),
+ ?line [{load_object_code,{test,"1.0",[foo,bar]}},
+ point_of_no_return,
+ {suspend,[foo,{bar,infinity}]},
+ {load,{bar,soft_purge,soft_purge}},
+ {load,{foo,soft_purge,soft_purge}},
+ {code_change,up,[{bar,[]}]},
+ {resume,[bar,foo]}] = X4b,
+
+ ?line {ok, X42b} = systools_rc:translate_scripts(dn,[Up4b], Apps, []),
+ ?line [{load_object_code,{test,"1.0",[foo,bar]}},
+ point_of_no_return,
+ {suspend,[foo,{bar,infinity}]},
+ {code_change,down,[{bar,[]}]},
+ {load,{foo,soft_purge,soft_purge}},
+ {load,{bar,soft_purge,soft_purge}},
+ {resume,[bar,foo]}] = X42b,
+
+ %% More complex dependency
+ Up5 = [{update, foo, soft, soft_purge, soft_purge, [bar, baz]},
+ {update, bar, {advanced, []}, soft_purge, soft_purge, []},
+ {update, baz, {advanced, baz}, soft_purge, soft_purge, [bar]}],
+ ?line {ok, X5} = systools_rc:translate_scripts([Up5], Apps, []),
+ ?line [{load_object_code,{test,"1.0",[foo,baz,bar]}},
+ point_of_no_return,
+ {suspend,[foo,baz,bar]},
+ {load,{bar,soft_purge,soft_purge}},
+ {load,{baz,soft_purge,soft_purge}},
+ {load,{foo,soft_purge,soft_purge}},
+ {code_change,up,[{baz,baz},{bar,[]}]},
+ {resume,[bar,baz,foo]}] = X5,
+
+ ?line {ok, X52} = systools_rc:translate_scripts(dn,[Up5], Apps, []),
+ ?line [{load_object_code,{test,"1.0",[foo,baz,bar]}},
+ point_of_no_return,
+ {suspend,[foo,baz,bar]},
+ {code_change,down,[{baz,baz},{bar,[]}]},
+ {load,{foo,soft_purge,soft_purge}},
+ {load,{baz,soft_purge,soft_purge}},
+ {load,{bar,soft_purge,soft_purge}},
+ {resume,[bar,baz,foo]}] = X52,
+
+ Up5a = [{update, foo, dynamic, infinity, soft, soft_purge,
+ soft_purge, [bar, baz]},
+ {update, bar, static, 7000, {advanced, []}, soft_purge,
+ soft_purge, []},
+ {update, baz, dynamic, default, {advanced, baz}, soft_purge,
+ soft_purge, [bar]}],
+ ?line {ok, X5a} = systools_rc:translate_scripts([Up5a], Apps, []),
+ ?line [{load_object_code,{test,"1.0",[foo,baz,bar]}},
+ point_of_no_return,
+ {suspend,[{foo,infinity},baz,{bar,7000}]},
+ {load,{bar,soft_purge,soft_purge}},
+ {load,{baz,soft_purge,soft_purge}},
+ {load,{foo,soft_purge,soft_purge}},
+ {code_change,up,[{baz,baz}, {bar,[]}]},
+ {resume,[bar,baz,foo]}] = X5a,
+
+ ?line {ok, X52a} = systools_rc:translate_scripts(dn,[Up5a], Apps, []),
+ ?line [{load_object_code,{test,"1.0",[foo,baz,bar]}},
+ point_of_no_return,
+ {suspend,[{foo,infinity},baz,{bar,7000}]},
+ {code_change,down,[{baz,baz}]},
+ {load,{foo,soft_purge,soft_purge}},
+ {load,{baz,soft_purge,soft_purge}},
+ {load,{bar,soft_purge,soft_purge}},
+ {code_change,down,[{bar,[]}]},
+ {resume,[bar,baz,foo]}] = X52a,
+
+ Up5b = [{update, foo, dynamic, infinity, soft, soft_purge,
+ soft_purge, [bar, baz]},
+ {update, bar, dynamic, 7000, {advanced, []}, soft_purge,
+ soft_purge, []},
+ {update, baz, static, default, {advanced, baz}, soft_purge,
+ soft_purge, [bar]}],
+ ?line {ok, X5b} = systools_rc:translate_scripts([Up5b], Apps, []),
+ ?line [{load_object_code,{test,"1.0",[foo,baz,bar]}},
+ point_of_no_return,
+ {suspend,[{foo,infinity},baz,{bar,7000}]},
+ {load,{bar,soft_purge,soft_purge}},
+ {load,{baz,soft_purge,soft_purge}},
+ {load,{foo,soft_purge,soft_purge}},
+ {code_change,up,[{baz,baz},{bar,[]}]},
+ {resume,[bar,baz,foo]}] = X5b,
+
+ ?line {ok, X52b} = systools_rc:translate_scripts(dn,[Up5b], Apps, []),
+ ?line [{load_object_code,{test,"1.0",[foo,baz,bar]}},
+ point_of_no_return,
+ {suspend,[{foo,infinity},baz,{bar,7000}]},
+ {code_change,down,[{bar,[]}]},
+ {load,{foo,soft_purge,soft_purge}},
+ {load,{baz,soft_purge,soft_purge}},
+ {load,{bar,soft_purge,soft_purge}},
+ {code_change,down,[{baz,baz}]},
+ {resume,[bar,baz,foo]}] = X52b,
+
+ %% Simple circular dependency (1)
+ Up6 = [{update, foo, soft, soft_purge, soft_purge, [bar]},
+ {update, bar, soft, soft_purge, soft_purge, [foo]}],
+ ?line {ok, X61} = systools_rc:translate_scripts([Up6], Apps, []),
+ ?line [{load_object_code,{test,"1.0",[foo,bar]}},
+ point_of_no_return,
+ {suspend,[foo,bar]},
+ {load,{bar,soft_purge,soft_purge}},
+ {load,{foo,soft_purge,soft_purge}},
+ {resume,[bar,foo]}] = X61,
+ ?line {ok, X62} = systools_rc:translate_scripts(dn,[Up6], Apps, []),
+ ?line [{load_object_code,{test,"1.0",[foo,bar]}},
+ point_of_no_return,
+ {suspend,[foo,bar]},
+ {load,{foo,soft_purge,soft_purge}},
+ {load,{bar,soft_purge,soft_purge}},
+ {resume,[bar,foo]}] = X62,
+
+ %% Simple circular dependency (2)
+ Up7 = [{update, foo, soft, soft_purge, soft_purge, [bar, baz]},
+ {update, bar, soft, soft_purge, soft_purge, [foo]},
+ {update, baz, soft, soft_purge, soft_purge, [bar]}],
+ ?line {ok, X71} = systools_rc:translate_scripts([Up7], Apps, []),
+ ?line [{load_object_code,{test,"1.0",[foo,bar,baz]}},
+ point_of_no_return,
+ {suspend,[foo,bar,baz]},
+ {load,{baz,soft_purge,soft_purge}},
+ {load,{bar,soft_purge,soft_purge}},
+ {load,{foo,soft_purge,soft_purge}},
+ {resume,[baz, bar, foo]}] = X71,
+ ?line {ok, X72} = systools_rc:translate_scripts(dn,[Up7], Apps, []),
+ ?line [{load_object_code,{test,"1.0",[foo,bar,baz]}},
+ point_of_no_return,
+ {suspend,[foo,bar,baz]},
+ {load,{foo,soft_purge,soft_purge}},
+ {load,{bar,soft_purge,soft_purge}},
+ {load,{baz,soft_purge,soft_purge}},
+ {resume,[baz,bar,foo]}] = X72,
+
+ %% Complex circular dependencies, check only order
+ %%
+ Up8 = [{update, foo, soft, soft_purge, soft_purge, [baz]},
+ {update, y, soft, soft_purge, soft_purge, [bar]},
+ {update, x, soft, soft_purge, soft_purge, [y, z]},
+ {update, z, soft, soft_purge, soft_purge, [x]},
+ {update, bar, soft, soft_purge, soft_purge, [baz]},
+ {update, baz, soft, soft_purge, soft_purge, [bar]}],
+ ?line {ok, X8} = systools_rc:translate_scripts([Up8], Apps, []),
+ ?line {value, {suspend, Order}} = lists:keysearch(suspend, 1, X8),
+ ?line Rest = case lists:reverse(Order) of
+ [bar, baz | R] -> R;
+ [baz, bar | R] -> R
+ end,
+ ?line case Rest of
+ [y, z, x, foo] -> ok;
+ [y, x, z, foo] -> ok;
+ [foo, y, z, x] -> ok;
+ [foo, y, x, z] -> ok;
+ [y, foo, z, x] -> ok;
+ [y, foo, x, z] -> ok
+ end,
+
+ %% Check that order among other instructions isn't changed
+ Up9 = [{update, foo, soft, soft_purge, soft_purge, [baz]},
+ {apply, {m, f, [1]}},
+ {update, y, soft, soft_purge, soft_purge, [bar]},
+ {apply, {m, f, [2]}},
+ {update, x, soft, soft_purge, soft_purge, [y, z]},
+ {apply, {m, f, [3]}},
+ {update, z, soft, soft_purge, soft_purge, [x]},
+ {apply, {m, f, [4]}},
+ {update, bar, soft, soft_purge, soft_purge, [baz]},
+ {apply, {m, f, [5]}},
+ {update, baz, soft, soft_purge, soft_purge, [bar]},
+ {apply, {m, f, [6]}}],
+ ?line {ok, X9} = systools_rc:translate_scripts([Up9], Apps, []),
+ Other2 = [X || {apply, {m, f, [X]}} <- X9],
+ ?line [1,2,3,4,5,6] = lists:sort(Other2),
+ ?line ok.
+
+
+translate_app(suite) -> [];
+translate_app(Config) when is_list(Config) ->
+ PreApps =
+ [#application{name = test,
+ description = "TEST",
+ vsn = "1.0",
+ modules = [{foo,1},{bar,1},{baz,1}],
+ regs = [],
+ mod = {sasl, []}},
+ #application{name = pelle,
+ description = "PELLE",
+ vsn = "1.0",
+ modules = [pelle, kalle],
+ regs = [],
+ mod = {pelle, []}}],
+ Apps =
+ [#application{name = test,
+ description = "TEST",
+ vsn = "1.0",
+ modules = [{foo,1},{bar,1},{baz,1}],
+ regs = [],
+ mod = {sasl, []}}],
+ %% Simple translation (1)
+ Up1 = [{add_module, foo},
+ {add_module, bar}],
+ ?line {ok, X1} = systools_rc:translate_scripts([Up1], Apps, []),
+ ?line [{load_object_code,{test,"1.0",[foo,bar]}},
+ point_of_no_return,
+ {load,{foo,brutal_purge,brutal_purge}},
+ {load,{bar,brutal_purge,brutal_purge}}] = X1,
+
+ %% Simple translation (2)
+ Up2 = [{add_application, test}],
+ ?line {ok, X2} = systools_rc:translate_scripts([Up2], Apps, []),
+io:format("X2=~p~n", [X2]),
+ ?line [{load_object_code,{test,"1.0",[foo,bar,baz]}},
+ point_of_no_return,
+ {load,{foo,brutal_purge,brutal_purge}},
+ {load,{bar,brutal_purge,brutal_purge}},
+ {load,{baz,brutal_purge,brutal_purge}},
+ {apply,{application,start,[test,permanent]}}] = X2,
+
+ %% Simple translation (3)
+ Up3 = [{remove_application, pelle}],
+ ?line {ok, X3} = systools_rc:translate_scripts([Up3], Apps, PreApps),
+ ?line [point_of_no_return,
+ {apply,{application,stop,[pelle]}},
+ {remove,{pelle,brutal_purge,brutal_purge}},
+ {remove,{kalle,brutal_purge,brutal_purge}},
+ {purge,[pelle,kalle]},
+ {apply,{application,unload,[pelle]}}] = X3,
+ ?line ok.
diff --git a/lib/snmp/src/manager/snmpm.erl b/lib/snmp/src/manager/snmpm.erl
index 49f7d66b9a..e457d3b47a 100644
--- a/lib/snmp/src/manager/snmpm.erl
+++ b/lib/snmp/src/manager/snmpm.erl
@@ -92,7 +92,7 @@
-export([format_reason/1, format_reason/2]).
-%% Backward compatibillity exports (API version "2")
+%% Backward compatibility exports (API version "2")
-export([
sync_get/3, sync_get/4, sync_get/5, sync_get/6,
async_get/3, async_get/4, async_get/5, async_get/6,
@@ -104,7 +104,7 @@
async_get_bulk/5, async_get_bulk/6, async_get_bulk/7, async_get_bulk/8
]).
-%% Backward compatibillity exports (API version "1")
+%% Backward compatibility exports (API version "1")
-deprecated({agent_info, 3}).
-deprecated({update_agent_info, 5}).
-deprecated({g, 3}).
@@ -387,23 +387,23 @@ register_agent(UserId, TargetName, Config)
is_list(Config)) ->
do_register_agent(UserId, TargetName, [{reg_type, target_name} | Config]);
-%% Backward compatibillity
+%% Backward compatibility
%% Note that the agent engine id is a mandatory config option,
%% so this function *will* fail!
register_agent(UserId, Addr, Port) when is_integer(Port) ->
register_agent(UserId, Addr, Port, []);
-%% Backward compatibillity
+%% Backward compatibility
register_agent(UserId, Addr, Config) when is_list(Config) ->
register_agent(UserId, Addr, ?DEFAULT_AGENT_PORT, Config).
-%% Backward compatibillity
+%% Backward compatibility
%% Note that the agent engine id is a mandatory config option,
%% so this function *will* fail!
register_agent(UserId, Addr) ->
register_agent(UserId, Addr, ?DEFAULT_AGENT_PORT, []).
-%% Backward compatibillity
+%% Backward compatibility
register_agent(UserId, Addr, Port, Config0) ->
case lists:keymember(target_name, 1, Config0) of
false ->
@@ -423,7 +423,7 @@ register_agent(UserId, Addr, Port, Config0) ->
unregister_agent(UserId, TargetName) when is_list(TargetName) ->
snmpm_config:unregister_agent(UserId, TargetName);
-%% Backward compatibillity functions
+%% Backward compatibility functions
unregister_agent(UserId, Addr) ->
unregister_agent(UserId, Addr, ?DEFAULT_AGENT_PORT).
@@ -438,7 +438,7 @@ unregister_agent(UserId, Addr, Port) ->
agent_info(TargetName, Item) ->
snmpm_config:agent_info(TargetName, Item).
-%% Backward compatibillity
+%% Backward compatibility
agent_info(Addr, Port, Item) ->
case target_name(Addr, Port) of
{ok, TargetName} ->
@@ -450,7 +450,7 @@ agent_info(Addr, Port, Item) ->
update_agent_info(UserId, TargetName, Item, Val) ->
snmpm_config:update_agent_info(UserId, TargetName, Item, Val).
-%% Backward compatibillity functions
+%% Backward compatibility functions
update_agent_info(UserId, Addr, Port, Item, Val) ->
case target_name(Addr, Port) of
{ok, TargetName} ->
@@ -1365,7 +1365,7 @@ cancel_async_request(UserId, ReqId) ->
%%%-----------------------------------------------------------------
-%%% Audit Trail Log functions (for backward compatibillity)
+%%% Audit Trail Log functions (for backward compatibility)
%%%-----------------------------------------------------------------
log_to_txt(LogDir, Mibs) ->
OutFile = "snmpm_log.txt",
diff --git a/lib/ssl/c_src/Makefile.in b/lib/ssl/c_src/Makefile.in
index 49a209f2eb..da716f7c40 100644
--- a/lib/ssl/c_src/Makefile.in
+++ b/lib/ssl/c_src/Makefile.in
@@ -28,6 +28,8 @@ include $(ERL_TOP)/make/$(TARGET)/otp.mk
# ----------------------------------------------------
SSL_LIBDIR = @SSL_LIBDIR@
SSL_INCLUDE = @SSL_INCLUDE@
+SSL_CRYPTO_LIBNAME = @SSL_CRYPTO_LIBNAME@
+SSL_SSL_LIBNAME = @SSL_SSL_LIBNAME@
# ----------------------------------------------------
# Application version
@@ -134,7 +136,7 @@ ifeq ($(findstring @,$(SSL_CC_RUNTIME_LIBRARY_PATH)),@)
SSL_CC_RUNTIME_LIBRARY_PATH = $(CC_R_OPT)
endif
-SSL_LINK_LIB=-L$(SSL_LIBDIR) -lssl -lcrypto
+SSL_LINK_LIB=-L$(SSL_LIBDIR) -l$(SSL_SSL_LIBNAME) -l$(SSL_CRYPTO_LIBNAME)
else
# not dynamic crypto lib (default from R11B-5)
NEED_KERBEROS=@SSL_LINK_WITH_KERBEROS@
@@ -142,7 +144,7 @@ NEED_ZLIB=@SSL_LINK_WITH_ZLIB@
SSL_MAKEFILE =
CC_R_OPT =
SSL_CC_RUNTIME_LIBRARY_PATH=
-SSL_LINK_LIB = $(SSL_LIBDIR)/libssl.a $(SSL_LIBDIR)/libcrypto.a
+SSL_LINK_LIB = $(SSL_LIBDIR)/lib$(SSL_SSL_LIBNAME).a $(SSL_LIBDIR)/lib$(SSL_CRYPTO_LIBNAME).a
ifeq ($(NEED_KERBEROS),yes)
SSL_LINK_LIB += @STATIC_KERBEROS_LIBS@
endif
@@ -175,7 +177,7 @@ $(BINDIR)/ssl_esock: $(OBJS)
# Win32/Cygwin
$(BINDIR)/ssl_esock.exe: $(OBJS)
- $(LD) $(SSL_CC_RUNTIME_LIBRARY_PATH) -L$(SSL_LIBDIR) -o $@ $^ -lwsock32 -llibeay32 -lssleay32
+ $(LD) $(SSL_CC_RUNTIME_LIBRARY_PATH) -L$(SSL_LIBDIR) -o $@ $^ -lwsock32 -l$(SSL_CRYPTO_LIBNAME) -l$(SSL_SSL_LIBNAME)
# Unix only, and only when linking statically
$(SSL_MAKEFILE):
diff --git a/lib/ssl/src/ssl_connection.erl b/lib/ssl/src/ssl_connection.erl
index 0a86e9bd29..2c452837f8 100644
--- a/lib/ssl/src/ssl_connection.erl
+++ b/lib/ssl/src/ssl_connection.erl
@@ -941,17 +941,23 @@ handle_info({Protocol, _, Data}, StateName,
handle_info({CloseTag, Socket}, _StateName,
#state{socket = Socket, close_tag = CloseTag,
- negotiated_version = Version, host = Host,
- port = Port, socket_options = Opts,
+ negotiated_version = Version,
+ socket_options = Opts,
user_application = {_Mon,Pid}, from = From,
- role = Role, session = Session} = State) ->
- %% Debug option maybe, the user do NOT want to see these in their logs
- %% error_logger:info_report("SSL: Peer did not send close notify alert."),
+ role = Role} = State) ->
+ %% Note that as of TLS 1.1,
+ %% failure to properly close a connection no longer requires that a
+ %% session not be resumed. This is a change from TLS 1.0 to conform
+ %% with widespread implementation practice.
case Version of
{1, N} when N >= 1 ->
ok;
_ ->
- invalidate_session(Role, Host, Port, Session)
+ %% As invalidate_sessions here causes performance issues,
+ %% we will conform to the widespread implementation
+ %% practice and go aginst the spec
+ %%invalidate_session(Role, Host, Port, Session)
+ ok
end,
alert_user(Opts#socket_options.active, Pid, From,
?ALERT_REC(?WARNING, ?CLOSE_NOTIFY), Role),
diff --git a/lib/stdlib/doc/src/re.xml b/lib/stdlib/doc/src/re.xml
index b8db55fc26..8c9e08ac3a 100644
--- a/lib/stdlib/doc/src/re.xml
+++ b/lib/stdlib/doc/src/re.xml
@@ -133,7 +133,7 @@ This option makes it possible to include comments inside complicated patterns. N
<tag><c>multiline</c></tag>
<item><p>By default, PCRE treats the subject string as consisting of a single line of characters (even if it actually contains newlines). The "start of line" metacharacter (^) matches only at the start of the string, while the "end of line" metacharacter ($) matches only at the end of the string, or before a terminating newline (unless <c>dollar_endonly</c> is given). This is the same as Perl.</p>
-<p>When <c>multiline</c> it is given, the "start of line" and "end of line" constructs match immediately following or immediately before internal newlines in the subject string, respectively, as well as at the very start and end. This is equivalent to Perl's /m option, and it can be changed within a pattern by a (?m) option setting. If there are no newlines in a subject string, or no occurrences of ^ or $ in a pattern, setting <c>multiline</c> has no effect.</p> </item>
+<p>When <c>multiline</c> is given, the "start of line" and "end of line" constructs match immediately following or immediately before internal newlines in the subject string, respectively, as well as at the very start and end. This is equivalent to Perl's /m option, and it can be changed within a pattern by a (?m) option setting. If there are no newlines in a subject string, or no occurrences of ^ or $ in a pattern, setting <c>multiline</c> has no effect.</p> </item>
<tag><c>no_auto_capture</c></tag>
<item>Disables the use of numbered capturing parentheses in the pattern. Any opening parenthesis that is not followed by ? behaves as if it were followed by ?: but named parentheses can still be used for capturing (and they acquire numbers in the usual way). There is no equivalent of this option in Perl.
</item>
diff --git a/lib/tools/src/cover.erl b/lib/tools/src/cover.erl
index 73a736f0e8..905ad895c9 100644
--- a/lib/tools/src/cover.erl
+++ b/lib/tools/src/cover.erl
@@ -662,6 +662,7 @@ main_process_loop(State) ->
Imported = do_import_to_table(Fd,File,
State#main_state.imported),
reply(From, ok),
+ file:close(Fd),
main_process_loop(State#main_state{imported=Imported});
{error,Reason} ->
reply(From, {error, {cant_open_file,File,Reason}}),
diff --git a/lib/tools/test/cover_SUITE.erl b/lib/tools/test/cover_SUITE.erl
index b5c8e8a1b7..fe7f92de78 100644
--- a/lib/tools/test/cover_SUITE.erl
+++ b/lib/tools/test/cover_SUITE.erl
@@ -395,6 +395,7 @@ export_import(suite) -> [];
export_import(Config) when is_list(Config) ->
?line DataDir = ?config(data_dir, Config),
?line ok = file:set_cwd(DataDir),
+ ?line PortCount = length(erlang:ports()),
%% Export one module
?line {ok,f} = cover:compile(f),
@@ -484,6 +485,9 @@ export_import(Config) when is_list(Config) ->
?line ?t:capture_stop(),
?line check_f_calls(1,0),
+ %% Check no raw files are left open
+ ?line PortCount = length(erlang:ports()),
+
%% Cleanup
?line ok = cover:stop(),
?line Files = lsfiles(),
diff --git a/lib/xmerl/src/xmerl_sax_parser.erl b/lib/xmerl/src/xmerl_sax_parser.erl
index c1acf51b8a..571feabee0 100644
--- a/lib/xmerl/src/xmerl_sax_parser.erl
+++ b/lib/xmerl/src/xmerl_sax_parser.erl
@@ -263,11 +263,11 @@ detect_charset_1(<<16#3C, 16#3F, 16#78, 16#6D, 16#6C, Xml2/binary>> = Xml, State
{Xml, State#xmerl_sax_parser_state{encoding=Enc}}
end;
_ ->
- {Xml, State#xmerl_sax_parser_state{encoding=utf8}}
+ {Xml, State}
end
end;
detect_charset_1(Xml, State) ->
- {Xml, State#xmerl_sax_parser_state{encoding=utf8}}.
+ {Xml, State}.
%%----------------------------------------------------------------------
%% Function: convert_encoding(Enc)
diff --git a/lib/xmerl/vsn.mk b/lib/xmerl/vsn.mk
index 280ff10efa..965a0ae7b4 100644
--- a/lib/xmerl/vsn.mk
+++ b/lib/xmerl/vsn.mk
@@ -1 +1 @@
-XMERL_VSN = 1.2.8
+XMERL_VSN = 1.2.9